mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-18 16:49:41 +03:00
feat(cfe,form): add borrowed form support across 6 skills
- cfe-borrow: borrow forms via Type.Name.Form.FormName, auto-borrow parent, generate Form.xml with BaseForm + metadata + empty Module.bsl - form-edit: formEvents, elementEvents, callType on events/commands, auto-detect extension mode (IDs 1000000+) - form-info: [EXTENSION] marker, callType on events/commands, BaseForm footer - form-validate: callType value checks, extension ID range warnings, BaseForm presence, callType-without-BaseForm detection - cfe-diff: form-level analysis in Mode A — borrowed/own forms, callType interceptors on events and commands - cfe-patch-method: warn if Form.xml missing for .Form. paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,9 +30,20 @@ allowed-tools:
|
|||||||
- `CommonModule.РаботаСФайлами` — общий модуль
|
- `CommonModule.РаботаСФайлами` — общий модуль
|
||||||
- `Document.РеализацияТоваров` — документ
|
- `Document.РеализацияТоваров` — документ
|
||||||
- `Enum.ВидыОплат` — перечисление
|
- `Enum.ВидыОплат` — перечисление
|
||||||
|
- `Catalog.Контрагенты.Form.ФормаЭлемента` — форма объекта (заимствование формы)
|
||||||
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
|
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
|
||||||
Поддерживаются все 44 типа объектов конфигурации.
|
Поддерживаются все 44 типа объектов конфигурации.
|
||||||
|
|
||||||
|
### Заимствование форм
|
||||||
|
|
||||||
|
Формат `Тип.Имя.Form.ИмяФормы` заимствует форму конкретного объекта. Если родительский объект ещё не заимствован — он будет заимствован автоматически.
|
||||||
|
|
||||||
|
Создаётся:
|
||||||
|
1. **Метаданные формы** — `Forms/ИмяФормы.xml` с `ObjectBelonging=Adopted`, `FormType=Managed`
|
||||||
|
2. **Form.xml** — `Forms/ИмяФормы/Ext/Form.xml` с копией исходной формы + `<BaseForm>` (начальное состояние)
|
||||||
|
3. **Module.bsl** — пустой файл `Forms/ИмяФормы/Ext/Form/Module.bsl`
|
||||||
|
4. **Регистрация** — `<Form>` в ChildObjects родительского объекта
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
@@ -45,6 +56,9 @@ powershell.exe -NoProfile -File .claude/skills/cfe-borrow/scripts/cfe-borrow.ps1
|
|||||||
# Заимствовать один объект
|
# Заимствовать один объект
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
||||||
|
|
||||||
|
# Заимствовать форму (автоматически заимствует родительский объект)
|
||||||
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты.Form.ФормаЭлемента"
|
||||||
|
|
||||||
# Несколько объектов за раз
|
# Несколько объектов за раз
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -384,6 +384,239 @@ function Read-SourceObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- 10b. Helper: read source form UUID ---
|
||||||
|
function Read-SourceFormUuid {
|
||||||
|
param([string]$typeName, [string]$objName, [string]$formName)
|
||||||
|
|
||||||
|
$dirName = $childTypeDirMap[$typeName]
|
||||||
|
$srcFile = Join-Path (Join-Path (Join-Path (Join-Path $cfgDir $dirName) $objName) "Forms") "${formName}.xml"
|
||||||
|
if (-not (Test-Path $srcFile)) {
|
||||||
|
Write-Error "Source form not found: $srcFile"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$srcDoc = New-Object System.Xml.XmlDocument
|
||||||
|
$srcDoc.PreserveWhitespace = $false
|
||||||
|
$srcDoc.Load($srcFile)
|
||||||
|
|
||||||
|
$srcEl = $null
|
||||||
|
foreach ($c in $srcDoc.DocumentElement.ChildNodes) {
|
||||||
|
if ($c.NodeType -eq 'Element') { $srcEl = $c; break }
|
||||||
|
}
|
||||||
|
if (-not $srcEl) {
|
||||||
|
Write-Error "No metadata element found in source form: $srcFile"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$srcUuid = $srcEl.GetAttribute("uuid")
|
||||||
|
if (-not $srcUuid) {
|
||||||
|
Write-Error "No uuid attribute on source form element: $srcFile"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return $srcUuid
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 10c. Helper: borrow a form ---
|
||||||
|
function Borrow-Form {
|
||||||
|
param([string]$typeName, [string]$objName, [string]$formName)
|
||||||
|
|
||||||
|
$dirName = $childTypeDirMap[$typeName]
|
||||||
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
|
||||||
|
# 1. Read source form UUID
|
||||||
|
$formUuid = Read-SourceFormUuid $typeName $objName $formName
|
||||||
|
Info " Source form UUID: $formUuid"
|
||||||
|
|
||||||
|
# 2. Read source Form.xml content
|
||||||
|
$srcFormXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $cfgDir $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
|
||||||
|
if (-not (Test-Path $srcFormXmlPath)) {
|
||||||
|
Write-Error "Source Form.xml not found: $srcFormXmlPath"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$srcFormContent = [System.IO.File]::ReadAllText($srcFormXmlPath, $enc)
|
||||||
|
|
||||||
|
# 3. Generate form metadata XML (ФормаЭлемента.xml)
|
||||||
|
$newFormUuid = [guid]::NewGuid().ToString()
|
||||||
|
$formMetaSb = New-Object System.Text.StringBuilder
|
||||||
|
$formMetaSb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t<Form uuid=`"${newFormUuid}`">") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t`t<InternalInfo/>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t`t<Properties>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t`t`t<Name>${formName}</Name>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t`t`t<Comment/>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t`t`t<ExtendedConfigurationObject>${formUuid}</ExtendedConfigurationObject>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t`t`t<FormType>Managed</FormType>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t`t</Properties>") | Out-Null
|
||||||
|
$formMetaSb.AppendLine("`t</Form>") | Out-Null
|
||||||
|
$formMetaSb.Append("</MetaDataObject>") | Out-Null
|
||||||
|
|
||||||
|
# 4. Create directories
|
||||||
|
$formMetaDir = Join-Path (Join-Path (Join-Path $extDir $dirName) $objName) "Forms"
|
||||||
|
if (-not (Test-Path $formMetaDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $formMetaDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write form metadata
|
||||||
|
$formMetaFile = Join-Path $formMetaDir "${formName}.xml"
|
||||||
|
[System.IO.File]::WriteAllText($formMetaFile, $formMetaSb.ToString(), $enc)
|
||||||
|
Info " Created: $formMetaFile"
|
||||||
|
|
||||||
|
# 5. Generate Form.xml with BaseForm
|
||||||
|
# Extract inner content from source (everything between <Form ...> and </Form>)
|
||||||
|
$innerContent = ""
|
||||||
|
$formVersion = "2.17"
|
||||||
|
if ($srcFormContent -match '(?s)<Form[^>]*version="([^"]*)"[^>]*>(.*)</Form>') {
|
||||||
|
$formVersion = $Matches[1]
|
||||||
|
$innerContent = $Matches[2]
|
||||||
|
} elseif ($srcFormContent -match '(?s)<Form[^>]*>(.*)</Form>') {
|
||||||
|
$innerContent = $Matches[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build the extension Form.xml: resultant form + <BaseForm>
|
||||||
|
$formXmlSb = New-Object System.Text.StringBuilder
|
||||||
|
# Copy the original XML declaration and <Form> opening tag
|
||||||
|
if ($srcFormContent -match '(?s)^(.*?<Form[^>]*>)') {
|
||||||
|
$formXmlSb.Append($Matches[1]) | Out-Null
|
||||||
|
}
|
||||||
|
# Resultant form content (same as source initially)
|
||||||
|
$formXmlSb.Append($innerContent) | Out-Null
|
||||||
|
# BaseForm section
|
||||||
|
$formXmlSb.AppendLine("`t<BaseForm version=`"${formVersion}`">") | Out-Null
|
||||||
|
# Inner content for BaseForm (trim leading newline)
|
||||||
|
$baseInner = $innerContent.TrimStart("`r", "`n")
|
||||||
|
$formXmlSb.Append("`t") | Out-Null
|
||||||
|
$formXmlSb.Append($baseInner) | Out-Null
|
||||||
|
# Close BaseForm — ensure it's on its own line
|
||||||
|
$lastChar = $formXmlSb.ToString()[-1]
|
||||||
|
if ($lastChar -ne "`n") { $formXmlSb.AppendLine() | Out-Null }
|
||||||
|
$formXmlSb.AppendLine("`t</BaseForm>") | Out-Null
|
||||||
|
$formXmlSb.Append("</Form>") | Out-Null
|
||||||
|
|
||||||
|
# Write Form.xml
|
||||||
|
$formXmlDir = Join-Path (Join-Path $formMetaDir $formName) "Ext"
|
||||||
|
if (-not (Test-Path $formXmlDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $formXmlDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
$formXmlFile = Join-Path $formXmlDir "Form.xml"
|
||||||
|
[System.IO.File]::WriteAllText($formXmlFile, $formXmlSb.ToString(), $enc)
|
||||||
|
Info " Created: $formXmlFile"
|
||||||
|
|
||||||
|
# 6. Create empty Module.bsl
|
||||||
|
$moduleDir = Join-Path $formXmlDir "Form"
|
||||||
|
if (-not (Test-Path $moduleDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $moduleDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
$moduleBslFile = Join-Path $moduleDir "Module.bsl"
|
||||||
|
[System.IO.File]::WriteAllText($moduleBslFile, "", $enc)
|
||||||
|
Info " Created: $moduleBslFile"
|
||||||
|
|
||||||
|
# 7. Register form in parent object ChildObjects
|
||||||
|
Register-FormInObject $typeName $objName $formName
|
||||||
|
|
||||||
|
return @($formMetaFile, $formXmlFile, $moduleBslFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 10d. Helper: register form in parent object's ChildObjects ---
|
||||||
|
function Register-FormInObject {
|
||||||
|
param([string]$typeName, [string]$objName, [string]$formName)
|
||||||
|
|
||||||
|
$dirName = $childTypeDirMap[$typeName]
|
||||||
|
$objFile = Join-Path (Join-Path $extDir $dirName) "${objName}.xml"
|
||||||
|
|
||||||
|
if (-not (Test-Path $objFile)) {
|
||||||
|
Warn "Parent object file not found: $objFile — form not registered in ChildObjects"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$objDoc = New-Object System.Xml.XmlDocument
|
||||||
|
$objDoc.PreserveWhitespace = $true
|
||||||
|
$objDoc.Load($objFile)
|
||||||
|
|
||||||
|
$objNs = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
|
||||||
|
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
|
||||||
|
# Find the type element
|
||||||
|
$objEl = $null
|
||||||
|
foreach ($c in $objDoc.DocumentElement.ChildNodes) {
|
||||||
|
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
||||||
|
}
|
||||||
|
if (-not $objEl) {
|
||||||
|
Warn "No type element in $objFile — form not registered"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find or create ChildObjects
|
||||||
|
$childObjs = $objEl.SelectSingleNode("md:ChildObjects", $objNs)
|
||||||
|
if (-not $childObjs) {
|
||||||
|
# Create ChildObjects element
|
||||||
|
$childObjs = $objDoc.CreateElement("ChildObjects", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
$objEl.AppendChild($objDoc.CreateWhitespace("`r`n`t`t")) | Out-Null
|
||||||
|
$objEl.AppendChild($childObjs) | Out-Null
|
||||||
|
$objEl.AppendChild($objDoc.CreateWhitespace("`r`n`t")) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check dedup
|
||||||
|
foreach ($c in $childObjs.ChildNodes) {
|
||||||
|
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq "Form" -and $c.InnerText -eq $formName) {
|
||||||
|
Warn "Form '$formName' already in ChildObjects of ${typeName}.${objName}"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Expand self-closing if needed
|
||||||
|
if (-not $childObjs.HasChildNodes -or $childObjs.IsEmpty) {
|
||||||
|
$closeWs = $objDoc.CreateWhitespace("`r`n`t`t")
|
||||||
|
$childObjs.AppendChild($closeWs) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add <Form>formName</Form>
|
||||||
|
$formEl = $objDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
$formEl.InnerText = $formName
|
||||||
|
|
||||||
|
$trailing = $childObjs.LastChild
|
||||||
|
$ws = $objDoc.CreateWhitespace("`r`n`t`t`t")
|
||||||
|
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
|
||||||
|
$childObjs.InsertBefore($ws, $trailing) | Out-Null
|
||||||
|
$childObjs.InsertBefore($formEl, $trailing) | Out-Null
|
||||||
|
} else {
|
||||||
|
$childObjs.AppendChild($ws) | Out-Null
|
||||||
|
$childObjs.AppendChild($formEl) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save object XML
|
||||||
|
$settings2 = New-Object System.Xml.XmlWriterSettings
|
||||||
|
$settings2.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
$settings2.Indent = $false
|
||||||
|
$settings2.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
|
|
||||||
|
$memStream2 = New-Object System.IO.MemoryStream
|
||||||
|
$writer2 = [System.Xml.XmlWriter]::Create($memStream2, $settings2)
|
||||||
|
$objDoc.Save($writer2)
|
||||||
|
$writer2.Flush(); $writer2.Close()
|
||||||
|
|
||||||
|
$bytes2 = $memStream2.ToArray()
|
||||||
|
$memStream2.Close()
|
||||||
|
$text2 = [System.Text.Encoding]::UTF8.GetString($bytes2)
|
||||||
|
if ($text2.Length -gt 0 -and $text2[0] -eq [char]0xFEFF) { $text2 = $text2.Substring(1) }
|
||||||
|
$text2 = $text2.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
|
||||||
|
$utf8Bom2 = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
[System.IO.File]::WriteAllText($objFile, $text2, $utf8Bom2)
|
||||||
|
Info " Registered form in: $objFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 10e. Helper: check if object is already borrowed in extension ---
|
||||||
|
function Test-ObjectBorrowed {
|
||||||
|
param([string]$typeName, [string]$objName)
|
||||||
|
|
||||||
|
$dirName = $childTypeDirMap[$typeName]
|
||||||
|
$objFile = Join-Path (Join-Path $extDir $dirName) "${objName}.xml"
|
||||||
|
return (Test-Path $objFile)
|
||||||
|
}
|
||||||
|
|
||||||
# --- 11. Helper: generate InternalInfo XML ---
|
# --- 11. Helper: generate InternalInfo XML ---
|
||||||
function Build-InternalInfoXml {
|
function Build-InternalInfoXml {
|
||||||
param([string]$typeName, [string]$objName, [string]$indent)
|
param([string]$typeName, [string]$objName, [string]$indent)
|
||||||
@@ -534,11 +767,11 @@ $borrowedCount = 0
|
|||||||
foreach ($item in $items) {
|
foreach ($item in $items) {
|
||||||
$dotIdx = $item.IndexOf(".")
|
$dotIdx = $item.IndexOf(".")
|
||||||
if ($dotIdx -lt 1) {
|
if ($dotIdx -lt 1) {
|
||||||
Write-Error "Invalid format '${item}', expected 'Type.Name'"
|
Write-Error "Invalid format '${item}', expected 'Type.Name' or 'Type.Name.Form.FormName'"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
$typeName = $item.Substring(0, $dotIdx)
|
$typeName = $item.Substring(0, $dotIdx)
|
||||||
$objName = $item.Substring($dotIdx + 1)
|
$remainder = $item.Substring($dotIdx + 1)
|
||||||
|
|
||||||
# Resolve Russian synonym to English type name
|
# Resolve Russian synonym to English type name
|
||||||
if ($synonymMap.ContainsKey($typeName)) { $typeName = $synonymMap[$typeName] }
|
if ($synonymMap.ContainsKey($typeName)) { $typeName = $synonymMap[$typeName] }
|
||||||
@@ -548,34 +781,71 @@ foreach ($item in $items) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$dirName = $childTypeDirMap[$typeName]
|
# Check for .Form. pattern: Type.ObjName.Form.FormName
|
||||||
|
$formName = $null
|
||||||
Info "Borrowing ${typeName}.${objName}..."
|
$formIdx = $remainder.IndexOf(".Form.")
|
||||||
|
if ($formIdx -gt 0) {
|
||||||
# Read source object
|
$objName = $remainder.Substring(0, $formIdx)
|
||||||
$src = Read-SourceObject $typeName $objName
|
$formName = $remainder.Substring($formIdx + 6) # skip ".Form."
|
||||||
Info " Source UUID: $($src.Uuid)"
|
} else {
|
||||||
|
$objName = $remainder
|
||||||
# Build borrowed object XML
|
|
||||||
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
|
|
||||||
|
|
||||||
# Create directory in extension if needed
|
|
||||||
$targetDir = Join-Path $extDir $dirName
|
|
||||||
if (-not (Test-Path $targetDir)) {
|
|
||||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Write borrowed object XML with UTF-8 BOM
|
$dirName = $childTypeDirMap[$typeName]
|
||||||
$targetFile = Join-Path $targetDir "${objName}.xml"
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
|
||||||
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
|
|
||||||
Info " Created: $targetFile"
|
|
||||||
|
|
||||||
# Add to ChildObjects
|
if ($formName) {
|
||||||
Add-ToChildObjects $typeName $objName
|
# --- Form borrowing ---
|
||||||
|
Info "Borrowing form ${typeName}.${objName}.Form.${formName}..."
|
||||||
|
|
||||||
$borrowedFiles += $targetFile
|
# Auto-borrow parent object if not yet borrowed
|
||||||
$borrowedCount++
|
if (-not (Test-ObjectBorrowed $typeName $objName)) {
|
||||||
|
Info " Parent object ${typeName}.${objName} not yet borrowed — borrowing first..."
|
||||||
|
|
||||||
|
$src = Read-SourceObject $typeName $objName
|
||||||
|
Info " Source UUID: $($src.Uuid)"
|
||||||
|
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
|
||||||
|
|
||||||
|
$targetDir = Join-Path $extDir $dirName
|
||||||
|
if (-not (Test-Path $targetDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
$targetFile = Join-Path $targetDir "${objName}.xml"
|
||||||
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
|
||||||
|
Info " Created: $targetFile"
|
||||||
|
|
||||||
|
Add-ToChildObjects $typeName $objName
|
||||||
|
$borrowedFiles += $targetFile
|
||||||
|
}
|
||||||
|
|
||||||
|
# Borrow the form
|
||||||
|
$formFiles = Borrow-Form $typeName $objName $formName
|
||||||
|
$borrowedFiles += $formFiles
|
||||||
|
$borrowedCount++
|
||||||
|
} else {
|
||||||
|
# --- Object borrowing (existing logic) ---
|
||||||
|
Info "Borrowing ${typeName}.${objName}..."
|
||||||
|
|
||||||
|
$src = Read-SourceObject $typeName $objName
|
||||||
|
Info " Source UUID: $($src.Uuid)"
|
||||||
|
|
||||||
|
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
|
||||||
|
|
||||||
|
$targetDir = Join-Path $extDir $dirName
|
||||||
|
if (-not (Test-Path $targetDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetFile = Join-Path $targetDir "${objName}.xml"
|
||||||
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
|
||||||
|
Info " Created: $targetFile"
|
||||||
|
|
||||||
|
Add-ToChildObjects $typeName $objName
|
||||||
|
|
||||||
|
$borrowedFiles += $targetFile
|
||||||
|
$borrowedCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 15. Save modified Configuration.xml ---
|
# --- 15. Save modified Configuration.xml ---
|
||||||
|
|||||||
@@ -38,9 +38,18 @@ powershell.exe -NoProfile -File .claude/skills/cfe-diff/scripts/cfe-diff.ps1 -Ex
|
|||||||
&ИзменениеИКонтроль("РеквизитыРедактируемыеВГрупповойОбработке") — line 4 in ...
|
&ИзменениеИКонтроль("РеквизитыРедактируемыеВГрупповойОбработке") — line 4 in ...
|
||||||
&Перед("ЗагрузитьКурсыВалют") — line 13 in ...
|
&Перед("ЗагрузитьКурсыВалют") — line 13 in ...
|
||||||
ChildObjects: 1 own attrs, 1 own TS, 3 own forms
|
ChildObjects: 1 own attrs, 1 own TS, 3 own forms
|
||||||
|
Form.ФормаЭлемента (borrowed):
|
||||||
|
Event:OnCreateAtServer [After] -> Расш1_ПриСозданииПосле
|
||||||
|
Command:Подбор [Before] -> Расш1_ПодборПеред
|
||||||
|
Form.Расш1_МояФорма (own)
|
||||||
[OWN] Catalog.Расш5_Справочник1
|
[OWN] Catalog.Расш5_Справочник1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Для каждой формы заимствованного объекта показывается:
|
||||||
|
- `(borrowed)` / `(own)` — заимствованная или собственная форма
|
||||||
|
- callType-события формы и элементов
|
||||||
|
- callType на командах
|
||||||
|
|
||||||
## Mode B — проверка переноса
|
## Mode B — проверка переноса
|
||||||
|
|
||||||
Для каждого `&ИзменениеИКонтроль` извлекает блоки `#Вставка`/`#КонецВставки` из расширения и ищет их в соответствующем модуле конфигурации.
|
Для каждого `&ИзменениеИКонтроль` извлекает блоки `#Вставка`/`#КонецВставки` из расширения и ищет их в соответствующем модуле конфигурации.
|
||||||
|
|||||||
@@ -210,6 +210,63 @@ function Get-InsertionBlocks {
|
|||||||
return $blocks
|
return $blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Helper: analyze form for callType events and commands ---
|
||||||
|
function Get-FormInterceptors {
|
||||||
|
param([string]$formXmlPath)
|
||||||
|
|
||||||
|
if (-not (Test-Path $formXmlPath)) { return $null }
|
||||||
|
|
||||||
|
$formDoc = New-Object System.Xml.XmlDocument
|
||||||
|
$formDoc.PreserveWhitespace = $false
|
||||||
|
try { $formDoc.Load($formXmlPath) } catch { return $null }
|
||||||
|
|
||||||
|
$fNs = New-Object System.Xml.XmlNamespaceManager($formDoc.NameTable)
|
||||||
|
$fNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
|
||||||
|
|
||||||
|
$fRoot = $formDoc.DocumentElement
|
||||||
|
$baseForm = $fRoot.SelectSingleNode("f:BaseForm", $fNs)
|
||||||
|
$isBorrowed = ($baseForm -ne $null)
|
||||||
|
|
||||||
|
$interceptors = @()
|
||||||
|
|
||||||
|
# Form-level events with callType
|
||||||
|
$eventsNode = $fRoot.SelectSingleNode("f:Events", $fNs)
|
||||||
|
if ($eventsNode) {
|
||||||
|
foreach ($evt in $eventsNode.SelectNodes("f:Event", $fNs)) {
|
||||||
|
$ct = $evt.GetAttribute("callType")
|
||||||
|
if ($ct) {
|
||||||
|
$interceptors += "Event:$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Element-level events with callType (scan all elements recursively)
|
||||||
|
$childItems = $fRoot.SelectSingleNode("f:ChildItems", $fNs)
|
||||||
|
if ($childItems) {
|
||||||
|
foreach ($evtNode in $childItems.SelectNodes(".//*[f:Events/f:Event[@callType]]", $fNs)) {
|
||||||
|
$elName = $evtNode.GetAttribute("name")
|
||||||
|
foreach ($evt in $evtNode.SelectNodes("f:Events/f:Event[@callType]", $fNs)) {
|
||||||
|
$ct = $evt.GetAttribute("callType")
|
||||||
|
$interceptors += "Element:${elName}.$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Commands with callType on Action
|
||||||
|
foreach ($cmd in $fRoot.SelectNodes("f:Commands/f:Command", $fNs)) {
|
||||||
|
$cmdName = $cmd.GetAttribute("name")
|
||||||
|
foreach ($action in $cmd.SelectNodes("f:Action[@callType]", $fNs)) {
|
||||||
|
$ct = $action.GetAttribute("callType")
|
||||||
|
$interceptors += "Command:$cmdName [$ct] -> $($action.InnerText)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{
|
||||||
|
IsBorrowed = $isBorrowed
|
||||||
|
Interceptors = $interceptors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# MODE A: Extension overview
|
# MODE A: Extension overview
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -255,6 +312,7 @@ if ($Mode -eq "A") {
|
|||||||
$ownForms = 0
|
$ownForms = 0
|
||||||
$ownTS = 0
|
$ownTS = 0
|
||||||
$borrowedItems = 0
|
$borrowedItems = 0
|
||||||
|
$formNames = @()
|
||||||
foreach ($c in $childObj.ChildNodes) {
|
foreach ($c in $childObj.ChildNodes) {
|
||||||
if ($c.NodeType -ne 'Element') { continue }
|
if ($c.NodeType -ne 'Element') { continue }
|
||||||
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
|
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
|
||||||
@@ -268,7 +326,7 @@ if ($Mode -eq "A") {
|
|||||||
switch ($c.LocalName) {
|
switch ($c.LocalName) {
|
||||||
"Attribute" { $ownAttrs++ }
|
"Attribute" { $ownAttrs++ }
|
||||||
"TabularSection" { $ownTS++ }
|
"TabularSection" { $ownTS++ }
|
||||||
"Form" { $ownForms++ }
|
"Form" { $formNames += $c.InnerText; $ownForms++ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$parts = @()
|
$parts = @()
|
||||||
@@ -279,6 +337,27 @@ if ($Mode -eq "A") {
|
|||||||
if ($parts.Count -gt 0) {
|
if ($parts.Count -gt 0) {
|
||||||
Write-Host " ChildObjects: $($parts -join ', ')"
|
Write-Host " ChildObjects: $($parts -join ', ')"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Analyze forms
|
||||||
|
$borrowedFormCount = 0
|
||||||
|
$ownFormCount = 0
|
||||||
|
foreach ($fn in $formNames) {
|
||||||
|
$formXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $info.DirName) $info.Name) "Forms") $fn) "Ext/Form.xml"
|
||||||
|
$fi = Get-FormInterceptors $formXmlPath
|
||||||
|
if (-not $fi) {
|
||||||
|
Write-Host " Form.$fn (?)"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$formTag = if ($fi.IsBorrowed) { "borrowed"; $borrowedFormCount++ } else { "own"; $ownFormCount++ }
|
||||||
|
if ($fi.Interceptors.Count -gt 0) {
|
||||||
|
Write-Host " Form.$fn ($formTag):"
|
||||||
|
foreach ($ic in $fi.Interceptors) {
|
||||||
|
Write-Host " $ic"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " Form.$fn ($formTag)"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -157,6 +157,21 @@ $bslCode += "$endKeyword"
|
|||||||
|
|
||||||
$bslText = ($bslCode -join "`r`n") + "`r`n"
|
$bslText = ($bslCode -join "`r`n") + "`r`n"
|
||||||
|
|
||||||
|
# --- Check form borrowing for .Form. paths ---
|
||||||
|
if ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
|
||||||
|
$formName = $parts[3]
|
||||||
|
$dirName = $typeDirMap[$objType]
|
||||||
|
$formMetaFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") "${formName}.xml"
|
||||||
|
$formXmlFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
|
||||||
|
|
||||||
|
if (-not (Test-Path $formMetaFile) -or -not (Test-Path $formXmlFile)) {
|
||||||
|
Write-Host "[WARN] Form '$formName' metadata or Form.xml not found in extension."
|
||||||
|
Write-Host " Run /cfe-borrow first:"
|
||||||
|
Write-Host " /cfe-borrow -ExtensionPath $ExtensionPath -ConfigPath <ConfigPath> -Object `"$objType.$objName.Form.$formName`""
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# --- Check if file exists and append ---
|
# --- Check if file exists and append ---
|
||||||
$bslDir = Split-Path $bslFile -Parent
|
$bslDir = Split-Path $bslFile -Parent
|
||||||
if (-not (Test-Path $bslDir)) {
|
if (-not (Test-Path $bslDir)) {
|
||||||
|
|||||||
@@ -50,6 +50,32 @@ powershell.exe -NoProfile -File .claude/skills/form-edit/scripts/form-edit.ps1 -
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Расширения (extension-формы)
|
||||||
|
|
||||||
|
Для заимствованных форм (с `<BaseForm>`) автоматически активируется extension-режим: ID начинаются с 1000000+. Доступны дополнительные секции:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"formEvents": [
|
||||||
|
{ "name": "OnCreateAtServer", "handler": "Расш1_ПриСозданииПосле", "callType": "After" },
|
||||||
|
{ "name": "OnOpen", "handler": "Расш1_ПриОткрытии", "callType": "Before" }
|
||||||
|
],
|
||||||
|
"elementEvents": [
|
||||||
|
{ "element": "Банк", "name": "OnChange", "handler": "Расш1_БанкПриИзменении", "callType": "Before" }
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
{ "name": "Подбор", "action": "Расш1_ПодборПосле", "callType": "After" },
|
||||||
|
{ "name": "Запрос", "actions": [
|
||||||
|
{ "callType": "Before", "handler": "Расш1_ЗапросПеред" },
|
||||||
|
{ "callType": "After", "handler": "Расш1_ЗапросПосле" }
|
||||||
|
]}
|
||||||
|
],
|
||||||
|
"elements": [
|
||||||
|
{ "input": "Поле", "path": "Объект.Поле", "on": [{ "event": "OnChange", "callType": "After" }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Позиционирование элементов
|
### Позиционирование элементов
|
||||||
|
|
||||||
| Ключ | По умолчанию | Описание |
|
| Ключ | По умолчанию | Описание |
|
||||||
@@ -96,19 +122,35 @@ powershell.exe -NoProfile -File .claude/skills/form-edit/scripts/form-edit.ps1 -
|
|||||||
|
|
||||||
`string`, `string(100)`, `decimal(15,2)`, `boolean`, `date`, `dateTime`, `CatalogRef.XXX`, `DocumentObject.XXX`, `ValueTable`, `DynamicList`, `Type1 | Type2` (составной).
|
`string`, `string(100)`, `decimal(15,2)`, `boolean`, `date`, `dateTime`, `CatalogRef.XXX`, `DocumentObject.XXX`, `ValueTable`, `DynamicList`, `Type1 | Type2` (составной).
|
||||||
|
|
||||||
|
### Секции расширений
|
||||||
|
|
||||||
|
| Секция | Назначение |
|
||||||
|
|--------|-----------|
|
||||||
|
| `formEvents` | События уровня формы с `callType` (Before/After/Override) |
|
||||||
|
| `elementEvents` | События на существующих элементах заимствованной формы |
|
||||||
|
| `callType` на `commands` | callType на Action команды |
|
||||||
|
| `callType` на `on` | callType на событиях новых элементов (объектный формат) |
|
||||||
|
|
||||||
|
Все extension-секции опциональны — без них навык работает как с обычными формами.
|
||||||
|
|
||||||
## Вывод
|
## Вывод
|
||||||
|
|
||||||
```
|
```
|
||||||
=== form-edit: Форма ===
|
=== form-edit: Форма ===
|
||||||
|
|
||||||
|
[EXTENSION] BaseForm detected — IDs start at 1000000+
|
||||||
|
|
||||||
|
Added form events:
|
||||||
|
+ OnCreateAtServer[After] -> Расш1_ПриСозданииПосле
|
||||||
|
|
||||||
Added elements (into ГруппаШапка, after Контрагент):
|
Added elements (into ГруппаШапка, after Контрагент):
|
||||||
+ [Input] Склад -> Объект.Склад {OnChange}
|
+ [Input] Склад -> Объект.Склад {OnChange}
|
||||||
|
|
||||||
Added attributes:
|
Added attributes:
|
||||||
+ СуммаИтого: decimal(15,2) (id=12)
|
+ СуммаИтого: decimal(15,2) (id=1000000)
|
||||||
|
|
||||||
---
|
---
|
||||||
Total: 1 element(s) (+2 companions), 1 attribute(s)
|
Total: 1 form event(s), 1 element(s) (+2 companions), 1 attribute(s)
|
||||||
Run /form-validate to verify.
|
Run /form-validate to verify.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,16 @@ $script:nextElemId++
|
|||||||
$script:nextAttrId++
|
$script:nextAttrId++
|
||||||
$script:nextCmdId++
|
$script:nextCmdId++
|
||||||
|
|
||||||
|
# --- 4b. Auto-detect extension mode (BaseForm present) ---
|
||||||
|
$script:isExtension = $false
|
||||||
|
$baseForm = $root.SelectSingleNode("f:BaseForm", $nsMgr)
|
||||||
|
if ($baseForm) {
|
||||||
|
$script:isExtension = $true
|
||||||
|
if ($script:nextAttrId -lt 1000000) { $script:nextAttrId = 1000000 }
|
||||||
|
if ($script:nextCmdId -lt 1000000) { $script:nextCmdId = 1000000 }
|
||||||
|
if ($script:nextElemId -lt 1000000) { $script:nextElemId = 1000000 }
|
||||||
|
}
|
||||||
|
|
||||||
function New-ElemId { $id = $script:nextElemId; $script:nextElemId++; return $id }
|
function New-ElemId { $id = $script:nextElemId; $script:nextElemId++; return $id }
|
||||||
function New-AttrId { $id = $script:nextAttrId; $script:nextAttrId++; return $id }
|
function New-AttrId { $id = $script:nextAttrId; $script:nextAttrId++; return $id }
|
||||||
function New-CmdId { $id = $script:nextCmdId; $script:nextCmdId++; return $id }
|
function New-CmdId { $id = $script:nextCmdId; $script:nextCmdId++; return $id }
|
||||||
@@ -260,18 +270,29 @@ function Emit-Events {
|
|||||||
if ($typeKey -and $script:knownEvents.ContainsKey($typeKey)) {
|
if ($typeKey -and $script:knownEvents.ContainsKey($typeKey)) {
|
||||||
$allowed = $script:knownEvents[$typeKey]
|
$allowed = $script:knownEvents[$typeKey]
|
||||||
foreach ($evt in $el.on) {
|
foreach ($evt in $el.on) {
|
||||||
if ($allowed.Count -gt 0 -and $allowed -notcontains "$evt") {
|
$evtStr = if ($evt -is [string]) { "$evt" } else { "$($evt.event)" }
|
||||||
Write-Host "[WARN] Unknown event '$evt' for $typeKey '$elementName'. Known: $($allowed -join ', ')"
|
if ($allowed.Count -gt 0 -and $allowed -notcontains $evtStr) {
|
||||||
|
Write-Host "[WARN] Unknown event '$evtStr' for $typeKey '$elementName'. Known: $($allowed -join ', ')"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
X "$indent<Events>"
|
X "$indent<Events>"
|
||||||
foreach ($evt in $el.on) {
|
foreach ($evt in $el.on) {
|
||||||
$evtName = "$evt"
|
# Support both string ("OnChange") and object ({ "event": "OnChange", "callType": "After" })
|
||||||
$handler = if ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" }
|
if ($evt -is [string] -or -not $evt.event) {
|
||||||
else { Get-HandlerName -elementName $elementName -eventName $evtName }
|
$evtName = "$evt"
|
||||||
X "$indent`t<Event name=`"$evtName`">$handler</Event>"
|
$handler = if ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" }
|
||||||
|
else { Get-HandlerName -elementName $elementName -eventName $evtName }
|
||||||
|
X "$indent`t<Event name=`"$evtName`">$handler</Event>"
|
||||||
|
} else {
|
||||||
|
$evtName = "$($evt.event)"
|
||||||
|
$handler = if ($evt.handler) { "$($evt.handler)" }
|
||||||
|
elseif ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" }
|
||||||
|
else { Get-HandlerName -elementName $elementName -eventName $evtName }
|
||||||
|
$callTypeAttr = if ($evt.callType) { " callType=`"$($evt.callType)`"" } else { "" }
|
||||||
|
X "$indent`t<Event name=`"$evtName`"$callTypeAttr>$handler</Event>"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
X "$indent</Events>"
|
X "$indent</Events>"
|
||||||
}
|
}
|
||||||
@@ -964,7 +985,20 @@ if ($def.commands -and $def.commands.Count -gt 0) {
|
|||||||
$inner = "$cmdChildIndent`t"
|
$inner = "$cmdChildIndent`t"
|
||||||
|
|
||||||
if ($cmd.title) { Emit-MLText -tag "Title" -text "$($cmd.title)" -indent $inner }
|
if ($cmd.title) { Emit-MLText -tag "Title" -text "$($cmd.title)" -indent $inner }
|
||||||
if ($cmd.action) { X "$inner<Action>$($cmd.action)</Action>" }
|
|
||||||
|
# Support single action with optional callType, or multiple actions
|
||||||
|
if ($cmd.actions) {
|
||||||
|
# Multiple actions: [{ "callType": "Before", "handler": "..." }, ...]
|
||||||
|
foreach ($act in $cmd.actions) {
|
||||||
|
$actHandler = "$($act.handler)"
|
||||||
|
$callTypeAttr = if ($act.callType) { " callType=`"$($act.callType)`"" } else { "" }
|
||||||
|
X "$inner<Action$callTypeAttr>$actHandler</Action>"
|
||||||
|
}
|
||||||
|
} elseif ($cmd.action) {
|
||||||
|
$callTypeAttr = if ($cmd.callType) { " callType=`"$($cmd.callType)`"" } else { "" }
|
||||||
|
X "$inner<Action$callTypeAttr>$($cmd.action)</Action>"
|
||||||
|
}
|
||||||
|
|
||||||
if ($cmd.shortcut) { X "$inner<Shortcut>$($cmd.shortcut)</Shortcut>" }
|
if ($cmd.shortcut) { X "$inner<Shortcut>$($cmd.shortcut)</Shortcut>" }
|
||||||
if ($cmd.picture) {
|
if ($cmd.picture) {
|
||||||
X "$inner<Picture>"
|
X "$inner<Picture>"
|
||||||
@@ -975,7 +1009,7 @@ if ($def.commands -and $def.commands.Count -gt 0) {
|
|||||||
if ($cmd.representation) { X "$inner<Representation>$($cmd.representation)</Representation>" }
|
if ($cmd.representation) { X "$inner<Representation>$($cmd.representation)</Representation>" }
|
||||||
|
|
||||||
X "$cmdChildIndent</Command>"
|
X "$cmdChildIndent</Command>"
|
||||||
$actionStr = if ($cmd.action) { " -> $($cmd.action)" } else { "" }
|
$actionStr = if ($cmd.action) { " -> $($cmd.action)" } elseif ($cmd.actions) { " -> $($cmd.actions.Count) action(s)" } else { "" }
|
||||||
$addedCmds += " + ${cmdName}${actionStr} (id=$cmdId)"
|
$addedCmds += " + ${cmdName}${actionStr} (id=$cmdId)"
|
||||||
}
|
}
|
||||||
X "</_F>"
|
X "</_F>"
|
||||||
@@ -988,6 +1022,127 @@ if ($def.commands -and $def.commands.Count -gt 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# === 12b. Add form-level events ===
|
||||||
|
|
||||||
|
$addedFormEvents = @()
|
||||||
|
|
||||||
|
if ($def.formEvents -and $def.formEvents.Count -gt 0) {
|
||||||
|
$eventsSection = $root.SelectSingleNode("f:Events", $nsMgr)
|
||||||
|
if (-not $eventsSection) {
|
||||||
|
# Create Events section — insert after AutoCommandBar or at the beginning
|
||||||
|
$eventsSection = $xmlDoc.CreateElement("Events", $formNs)
|
||||||
|
$insertAfter = $root.SelectSingleNode("f:AutoCommandBar", $nsMgr)
|
||||||
|
if ($insertAfter) {
|
||||||
|
$refNode = $insertAfter
|
||||||
|
$ws = $xmlDoc.CreateWhitespace("`r`n`t")
|
||||||
|
# Insert before the AutoCommandBar (Events come before AutoCommandBar in 1C)
|
||||||
|
$root.InsertBefore($ws, $refNode) | Out-Null
|
||||||
|
$root.InsertBefore($eventsSection, $refNode) | Out-Null
|
||||||
|
} else {
|
||||||
|
$firstChild = $root.FirstChild
|
||||||
|
if ($firstChild) {
|
||||||
|
$ws = $xmlDoc.CreateWhitespace("`r`n`t")
|
||||||
|
$root.InsertBefore($eventsSection, $firstChild) | Out-Null
|
||||||
|
$root.InsertBefore($ws, $eventsSection) | Out-Null
|
||||||
|
} else {
|
||||||
|
$root.AppendChild($xmlDoc.CreateWhitespace("`r`n`t")) | Out-Null
|
||||||
|
$root.AppendChild($eventsSection) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$evtChildIndent = Get-ChildIndent $eventsSection
|
||||||
|
if (-not $evtChildIndent -or $evtChildIndent -eq "") { $evtChildIndent = "`t`t" }
|
||||||
|
|
||||||
|
# Generate event fragments
|
||||||
|
$script:xml = New-Object System.Text.StringBuilder 512
|
||||||
|
X "<_F $allNsDecl>"
|
||||||
|
foreach ($fe in $def.formEvents) {
|
||||||
|
$feName = "$($fe.name)"
|
||||||
|
$feHandler = "$($fe.handler)"
|
||||||
|
$callTypeAttr = if ($fe.callType) { " callType=`"$($fe.callType)`"" } else { "" }
|
||||||
|
X "$evtChildIndent<Event name=`"$feName`"$callTypeAttr>$feHandler</Event>"
|
||||||
|
$ctStr = if ($fe.callType) { "[$($fe.callType)]" } else { "" }
|
||||||
|
$addedFormEvents += " + $feName${ctStr} -> $feHandler"
|
||||||
|
}
|
||||||
|
X "</_F>"
|
||||||
|
|
||||||
|
$fragDoc = Parse-Fragment $script:xml.ToString()
|
||||||
|
$importedEvents = Import-ElementNodes $fragDoc
|
||||||
|
|
||||||
|
foreach ($node in $importedEvents) {
|
||||||
|
Insert-IntoContainer -container $eventsSection -newNode $node -afterName $null -childIndent $evtChildIndent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# === 12c. Add element-level events ===
|
||||||
|
|
||||||
|
$addedElemEvents = @()
|
||||||
|
|
||||||
|
if ($def.elementEvents -and $def.elementEvents.Count -gt 0) {
|
||||||
|
if (-not $rootCI) {
|
||||||
|
$rootCI = $root.SelectSingleNode("f:ChildItems", $nsMgr)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($ee in $def.elementEvents) {
|
||||||
|
$targetName = "$($ee.element)"
|
||||||
|
$targetEl = Find-Element $rootCI $targetName
|
||||||
|
if (-not $targetEl) {
|
||||||
|
Write-Host "[WARN] Element '$targetName' not found — skipping elementEvent"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find or create Events element within the target
|
||||||
|
$targetEvents = $targetEl.SelectSingleNode("f:Events", $nsMgr)
|
||||||
|
if (-not $targetEvents) {
|
||||||
|
$targetEvents = $xmlDoc.CreateElement("Events", $formNs)
|
||||||
|
# Insert Events before closing tag (after last property, before ChildItems if any)
|
||||||
|
$ciNode = $targetEl.SelectSingleNode("f:ChildItems", $nsMgr)
|
||||||
|
if ($ciNode) {
|
||||||
|
$ws = $xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl))
|
||||||
|
$targetEl.InsertBefore($ws, $ciNode) | Out-Null
|
||||||
|
$targetEl.InsertBefore($targetEvents, $ciNode) | Out-Null
|
||||||
|
} else {
|
||||||
|
$trailing = $targetEl.LastChild
|
||||||
|
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
|
||||||
|
$ws = $xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl))
|
||||||
|
$targetEl.InsertBefore($ws, $trailing) | Out-Null
|
||||||
|
$targetEl.InsertBefore($targetEvents, $trailing) | Out-Null
|
||||||
|
} else {
|
||||||
|
$targetEl.AppendChild($xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl))) | Out-Null
|
||||||
|
$targetEl.AppendChild($targetEvents) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$eeChildIndent = Get-ChildIndent $targetEvents
|
||||||
|
if (-not $eeChildIndent -or $eeChildIndent -eq "") {
|
||||||
|
$parentIndent = Get-ChildIndent $targetEl
|
||||||
|
$eeChildIndent = "$parentIndent`t"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create Event element
|
||||||
|
$eeName = "$($ee.name)"
|
||||||
|
$eeHandler = "$($ee.handler)"
|
||||||
|
$callTypeAttr = if ($ee.callType) { " callType=`"$($ee.callType)`"" } else { "" }
|
||||||
|
|
||||||
|
$script:xml = New-Object System.Text.StringBuilder 256
|
||||||
|
X "<_F $allNsDecl>"
|
||||||
|
X "$eeChildIndent<Event name=`"$eeName`"$callTypeAttr>$eeHandler</Event>"
|
||||||
|
X "</_F>"
|
||||||
|
|
||||||
|
$fragDoc = Parse-Fragment $script:xml.ToString()
|
||||||
|
$importedEE = Import-ElementNodes $fragDoc
|
||||||
|
|
||||||
|
foreach ($node in $importedEE) {
|
||||||
|
Insert-IntoContainer -container $targetEvents -newNode $node -afterName $null -childIndent $eeChildIndent
|
||||||
|
}
|
||||||
|
|
||||||
|
$ctStr = if ($ee.callType) { "[$($ee.callType)]" } else { "" }
|
||||||
|
$addedElemEvents += " + $targetName.$eeName${ctStr} -> $eeHandler"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# === 13. Save ===
|
# === 13. Save ===
|
||||||
|
|
||||||
$content = $xmlDoc.OuterXml
|
$content = $xmlDoc.OuterXml
|
||||||
@@ -999,6 +1154,23 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
|||||||
|
|
||||||
# === 14. Summary ===
|
# === 14. Summary ===
|
||||||
|
|
||||||
|
if ($script:isExtension) {
|
||||||
|
Write-Host "[EXTENSION] BaseForm detected — IDs start at 1000000+"
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($addedFormEvents.Count -gt 0) {
|
||||||
|
Write-Host "Added form events:"
|
||||||
|
foreach ($line in $addedFormEvents) { Write-Host $line }
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($addedElemEvents.Count -gt 0) {
|
||||||
|
Write-Host "Added element events:"
|
||||||
|
foreach ($line in $addedElemEvents) { Write-Host $line }
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
if ($addedElems.Count -gt 0) {
|
if ($addedElems.Count -gt 0) {
|
||||||
$posStr = ""
|
$posStr = ""
|
||||||
if ($def.into) { $posStr += "into $($def.into)" }
|
if ($def.into) { $posStr += "into $($def.into)" }
|
||||||
@@ -1023,6 +1195,8 @@ if ($addedCmds.Count -gt 0) {
|
|||||||
|
|
||||||
Write-Host "---"
|
Write-Host "---"
|
||||||
$totalParts = @()
|
$totalParts = @()
|
||||||
|
if ($addedFormEvents.Count -gt 0) { $totalParts += "$($addedFormEvents.Count) form event(s)" }
|
||||||
|
if ($addedElemEvents.Count -gt 0) { $totalParts += "$($addedElemEvents.Count) element event(s)" }
|
||||||
if ($addedElems.Count -gt 0) {
|
if ($addedElems.Count -gt 0) {
|
||||||
$compStr = if ($companionCount -gt 0) { " (+$companionCount companions)" } else { "" }
|
$compStr = if ($companionCount -gt 0) { " (+$companionCount companions)" } else { "" }
|
||||||
$totalParts += "$($addedElems.Count) element(s)$compStr"
|
$totalParts += "$($addedElems.Count) element(s)$compStr"
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ powershell.exe -NoProfile -File .claude/skills/form-info/scripts/form-info.ps1 -
|
|||||||
=== Form: ФормаДокумента — "Реализация товаров и услуг" (Documents.РеализацияТоваровУслуг) ===
|
=== Form: ФормаДокумента — "Реализация товаров и услуг" (Documents.РеализацияТоваровУслуг) ===
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Для заимствованных форм расширения (с `<BaseForm>`):
|
||||||
|
```
|
||||||
|
=== Form: ФормаЭлемента [EXTENSION] (Catalogs.Валюты) ===
|
||||||
|
```
|
||||||
|
|
||||||
Имя формы, заголовок (Title) и контекст объекта определяются из пути к файлу и XML.
|
Имя формы, заголовок (Title) и контекст объекта определяются из пути к файлу и XML.
|
||||||
|
|
||||||
### Properties — свойства формы
|
### Properties — свойства формы
|
||||||
@@ -63,6 +68,13 @@ Events:
|
|||||||
OnOpen -> ПриОткрытии
|
OnOpen -> ПриОткрытии
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Для расширений с callType:
|
||||||
|
```
|
||||||
|
Events:
|
||||||
|
OnCreateAtServer[After] -> Расш1_ПриСозданииПосле
|
||||||
|
OnOpen[Before] -> Расш1_ПриОткрытии
|
||||||
|
```
|
||||||
|
|
||||||
### Elements — дерево UI-элементов
|
### Elements — дерево UI-элементов
|
||||||
|
|
||||||
Компактное дерево с типами, привязками к данным, флагами и событиями:
|
Компактное дерево с типами, привязками к данным, флагами и событиями:
|
||||||
@@ -114,7 +126,7 @@ Elements:
|
|||||||
|
|
||||||
**Привязка к команде**: `-> ИмяКоманды [cmd]` — команда формы, `-> Close [std]` — стандартная команда
|
**Привязка к команде**: `-> ИмяКоманды [cmd]` — команда формы, `-> Close [std]` — стандартная команда
|
||||||
|
|
||||||
**События**: `{OnChange, StartChoice}` — имена обработчиков
|
**События**: `{OnChange, StartChoice}` — имена обработчиков; `{OnChange[Before]}` — с callType для расширений
|
||||||
|
|
||||||
**Заголовок**: `[title:Текст]` — только если отличается от имени элемента
|
**Заголовок**: `[title:Текст]` — только если отличается от имени элемента
|
||||||
|
|
||||||
@@ -151,8 +163,21 @@ Commands:
|
|||||||
Заполнить -> ЗаполнитьОбработка
|
Заполнить -> ЗаполнитьОбработка
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Для расширений с callType на Action:
|
||||||
|
```
|
||||||
|
Commands:
|
||||||
|
Подбор -> Расш1_ПодборПеред[Before], Расш1_ПодборПосле[After]
|
||||||
|
```
|
||||||
|
|
||||||
Формат: `Имя -> Обработчик [Сочетание]`
|
Формат: `Имя -> Обработчик [Сочетание]`
|
||||||
|
|
||||||
|
### BaseForm (расширения)
|
||||||
|
|
||||||
|
Для заимствованных форм в конце выводится:
|
||||||
|
```
|
||||||
|
BaseForm: present (version 2.17)
|
||||||
|
```
|
||||||
|
|
||||||
## Что пропускается
|
## Что пропускается
|
||||||
|
|
||||||
Скрипт убирает 80%+ XML-объёма:
|
Скрипт убирает 80%+ XML-объёма:
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ $ns.AddNamespace("dcsset", "http://v8.1c.ru/8.1/data-composition-system/settings
|
|||||||
|
|
||||||
$root = $xmlDoc.DocumentElement
|
$root = $xmlDoc.DocumentElement
|
||||||
|
|
||||||
|
# --- Detect extension (BaseForm) ---
|
||||||
|
$baseFormNode = $root.SelectSingleNode("d:BaseForm", $ns)
|
||||||
|
$isExtension = ($baseFormNode -ne $null)
|
||||||
|
|
||||||
# --- Helper: extract multilang text ---
|
# --- Helper: extract multilang text ---
|
||||||
|
|
||||||
function Get-MLText($node) {
|
function Get-MLText($node) {
|
||||||
@@ -140,7 +144,10 @@ function Get-EventsStr($node) {
|
|||||||
if (-not $eventsNode) { return "" }
|
if (-not $eventsNode) { return "" }
|
||||||
$evts = @()
|
$evts = @()
|
||||||
foreach ($e in $eventsNode.SelectNodes("d:Event", $ns)) {
|
foreach ($e in $eventsNode.SelectNodes("d:Event", $ns)) {
|
||||||
$evts += $e.GetAttribute("name")
|
$eName = $e.GetAttribute("name")
|
||||||
|
$ct = $e.GetAttribute("callType")
|
||||||
|
if ($ct) { $evts += "$eName[$ct]" }
|
||||||
|
else { $evts += $eName }
|
||||||
}
|
}
|
||||||
if ($evts.Count -eq 0) { return "" }
|
if ($evts.Count -eq 0) { return "" }
|
||||||
return " {$($evts -join ', ')}"
|
return " {$($evts -join ', ')}"
|
||||||
@@ -340,7 +347,8 @@ if ($titleNode) {
|
|||||||
$formTitle = Get-MLText $titleNode
|
$formTitle = Get-MLText $titleNode
|
||||||
if (-not $formTitle) { $formTitle = $titleNode.InnerText }
|
if (-not $formTitle) { $formTitle = $titleNode.InnerText }
|
||||||
}
|
}
|
||||||
$header = "=== Form: $formName"
|
$extMarker = if ($isExtension) { " [EXTENSION]" } else { "" }
|
||||||
|
$header = "=== Form: $formName$extMarker"
|
||||||
if ($formTitle) { $header += " — `"$formTitle`"" }
|
if ($formTitle) { $header += " — `"$formTitle`"" }
|
||||||
if ($objectContext) { $header += " ($objectContext)" }
|
if ($objectContext) { $header += " ($objectContext)" }
|
||||||
$header += " ==="
|
$header += " ==="
|
||||||
@@ -390,7 +398,9 @@ if ($formEvents -and $formEvents.HasChildNodes) {
|
|||||||
foreach ($e in $formEvents.SelectNodes("d:Event", $ns)) {
|
foreach ($e in $formEvents.SelectNodes("d:Event", $ns)) {
|
||||||
$eName = $e.GetAttribute("name")
|
$eName = $e.GetAttribute("name")
|
||||||
$eHandler = $e.InnerText
|
$eHandler = $e.InnerText
|
||||||
$lines += " $eName -> $eHandler"
|
$ct = $e.GetAttribute("callType")
|
||||||
|
$ctStr = if ($ct) { "[$ct]" } else { "" }
|
||||||
|
$lines += " $eName${ctStr} -> $eHandler"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,12 +501,27 @@ if ($cmdsNode) {
|
|||||||
$cmdLines = @()
|
$cmdLines = @()
|
||||||
foreach ($cmd in $cmdsNode.SelectNodes("d:Command", $ns)) {
|
foreach ($cmd in $cmdsNode.SelectNodes("d:Command", $ns)) {
|
||||||
$cName = $cmd.GetAttribute("name")
|
$cName = $cmd.GetAttribute("name")
|
||||||
$action = $cmd.SelectSingleNode("d:Action", $ns)
|
|
||||||
$shortcut = $cmd.SelectSingleNode("d:Shortcut", $ns)
|
$shortcut = $cmd.SelectSingleNode("d:Shortcut", $ns)
|
||||||
|
|
||||||
$actionStr = if ($action) { " -> $($action.InnerText)" } else { "" }
|
|
||||||
$scStr = if ($shortcut) { " [$($shortcut.InnerText)]" } else { "" }
|
$scStr = if ($shortcut) { " [$($shortcut.InnerText)]" } else { "" }
|
||||||
|
|
||||||
|
# Collect all Action elements (may have multiple with callType)
|
||||||
|
$actions = $cmd.SelectNodes("d:Action", $ns)
|
||||||
|
if ($actions.Count -gt 1) {
|
||||||
|
$actParts = @()
|
||||||
|
foreach ($a in $actions) {
|
||||||
|
$ct = $a.GetAttribute("callType")
|
||||||
|
$ctStr = if ($ct) { "[$ct]" } else { "" }
|
||||||
|
$actParts += "$($a.InnerText)$ctStr"
|
||||||
|
}
|
||||||
|
$actionStr = " -> $($actParts -join ', ')"
|
||||||
|
} elseif ($actions.Count -eq 1) {
|
||||||
|
$ct = $actions[0].GetAttribute("callType")
|
||||||
|
$ctStr = if ($ct) { "[$ct]" } else { "" }
|
||||||
|
$actionStr = " -> $($actions[0].InnerText)$ctStr"
|
||||||
|
} else {
|
||||||
|
$actionStr = ""
|
||||||
|
}
|
||||||
|
|
||||||
$cmdLines += " $cName$actionStr$scStr"
|
$cmdLines += " $cName$actionStr$scStr"
|
||||||
}
|
}
|
||||||
if ($cmdLines.Count -gt 0) {
|
if ($cmdLines.Count -gt 0) {
|
||||||
@@ -506,6 +531,15 @@ if ($cmdsNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- BaseForm footer ---
|
||||||
|
|
||||||
|
if ($isExtension) {
|
||||||
|
$bfVersion = $baseFormNode.GetAttribute("version")
|
||||||
|
$bfStr = if ($bfVersion) { "present (version $bfVersion)" } else { "present" }
|
||||||
|
$lines += ""
|
||||||
|
$lines += "BaseForm: $bfStr"
|
||||||
|
}
|
||||||
|
|
||||||
# --- Truncation protection ---
|
# --- Truncation protection ---
|
||||||
|
|
||||||
$totalLines = $lines.Count
|
$totalLines = $lines.Count
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ powershell.exe -NoProfile -File .claude/skills/form-validate/scripts/form-valida
|
|||||||
| 9 | События имеют непустые имена обработчиков | ERROR |
|
| 9 | События имеют непустые имена обработчиков | ERROR |
|
||||||
| 10 | Команды имеют Action (обработчик) | ERROR |
|
| 10 | Команды имеют Action (обработчик) | ERROR |
|
||||||
| 11 | Не более одного MainAttribute | ERROR |
|
| 11 | Не более одного MainAttribute | ERROR |
|
||||||
|
| 12 | BaseForm: наличие и version (при расширении) | OK / WARN |
|
||||||
|
| 13 | callType значения: Before, After, Override | ERROR |
|
||||||
|
| 14 | ID расширения >= 1000000 для добавленных attrs/commands | WARN |
|
||||||
|
| 15 | callType без BaseForm — некорректная структура | WARN |
|
||||||
|
|
||||||
## Вывод
|
## Вывод
|
||||||
|
|
||||||
@@ -71,8 +75,18 @@ All checks passed.
|
|||||||
|
|
||||||
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
|
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
|
||||||
|
|
||||||
|
### Расширения
|
||||||
|
|
||||||
|
При обнаружении `<BaseForm>` автоматически активируются дополнительные проверки:
|
||||||
|
- Валидность значений `callType` (Before/After/Override)
|
||||||
|
- ID расширения >= 1000000 для добавленных атрибутов и команд
|
||||||
|
- Наличие version на `<BaseForm>`
|
||||||
|
|
||||||
|
Формы без `<BaseForm>` проверяются только стандартными проверками.
|
||||||
|
|
||||||
## Когда использовать
|
## Когда использовать
|
||||||
|
|
||||||
- **После `/form-compile`**: проверить корректность сгенерированной формы
|
- **После `/form-compile`**: проверить корректность сгенерированной формы
|
||||||
|
- **После `/form-edit`**: проверить добавленные элементы, особенно в extension-формах
|
||||||
- **После ручного редактирования Form.xml**: убедиться что ID уникальны, companions на месте, ссылки валидны
|
- **После ручного редактирования Form.xml**: убедиться что ID уникальны, companions на месте, ссылки валидны
|
||||||
- **При отладке**: выявить ошибки в структуре формы до сборки EPF
|
- **При отладке**: выявить ошибки в структуре формы до сборки EPF
|
||||||
|
|||||||
@@ -479,6 +479,152 @@ if (-not $stopped) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Check 11: Extension-specific validations ---
|
||||||
|
|
||||||
|
$baseFormNode = $root.SelectSingleNode("f:BaseForm", $nsMgr)
|
||||||
|
$isExtension = ($baseFormNode -ne $null)
|
||||||
|
|
||||||
|
if (-not $stopped -and $isExtension) {
|
||||||
|
# 11a. BaseForm version
|
||||||
|
$bfVersion = $baseFormNode.GetAttribute("version")
|
||||||
|
if ($bfVersion) {
|
||||||
|
Report-OK "BaseForm: version=$bfVersion"
|
||||||
|
} else {
|
||||||
|
Report-Warn "BaseForm: version attribute missing"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 11b. callType values validation (Before, After, Override)
|
||||||
|
$validCallTypes = @("Before", "After", "Override")
|
||||||
|
$ctErrors = 0
|
||||||
|
$ctChecked = 0
|
||||||
|
|
||||||
|
# Check form-level events
|
||||||
|
$formEventsNode = $root.SelectSingleNode("f:Events", $nsMgr)
|
||||||
|
if ($formEventsNode) {
|
||||||
|
foreach ($evt in $formEventsNode.SelectNodes("f:Event", $nsMgr)) {
|
||||||
|
$ct = $evt.GetAttribute("callType")
|
||||||
|
if ($ct) {
|
||||||
|
$ctChecked++
|
||||||
|
if ($validCallTypes -notcontains $ct) {
|
||||||
|
Report-Error "Form event '$($evt.GetAttribute('name'))': invalid callType='$ct' (expected: Before, After, Override)"
|
||||||
|
$ctErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check element-level events
|
||||||
|
foreach ($el in $allElements) {
|
||||||
|
if ($stopped) { break }
|
||||||
|
$eventsNode = $el.Node.SelectSingleNode("f:Events", $nsMgr)
|
||||||
|
if (-not $eventsNode) { continue }
|
||||||
|
foreach ($evt in $eventsNode.SelectNodes("f:Event", $nsMgr)) {
|
||||||
|
$ct = $evt.GetAttribute("callType")
|
||||||
|
if ($ct) {
|
||||||
|
$ctChecked++
|
||||||
|
if ($validCallTypes -notcontains $ct) {
|
||||||
|
Report-Error "[$($el.Tag)] '$($el.Name)' event '$($evt.GetAttribute('name'))': invalid callType='$ct'"
|
||||||
|
$ctErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check command actions
|
||||||
|
foreach ($cmd in $cmdNodes) {
|
||||||
|
if ($stopped) { break }
|
||||||
|
$cmdName = $cmd.GetAttribute("name")
|
||||||
|
foreach ($action in $cmd.SelectNodes("f:Action", $nsMgr)) {
|
||||||
|
$ct = $action.GetAttribute("callType")
|
||||||
|
if ($ct) {
|
||||||
|
$ctChecked++
|
||||||
|
if ($validCallTypes -notcontains $ct) {
|
||||||
|
Report-Error "Command '$cmdName' Action: invalid callType='$ct'"
|
||||||
|
$ctErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $stopped -and $ctErrors -eq 0 -and $ctChecked -gt 0) {
|
||||||
|
Report-OK "callType values: $ctChecked checked"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 11c. Extension ID ranges — warn if extension-added attrs/commands have id < 1000000
|
||||||
|
# Collect BaseForm attribute names to distinguish added ones
|
||||||
|
$baseAttrNames = @{}
|
||||||
|
$baseCmdNames = @{}
|
||||||
|
$bfNs = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||||
|
$bfNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
|
||||||
|
foreach ($bAttr in $baseFormNode.SelectNodes("f:Attributes/f:Attribute", $bfNs)) {
|
||||||
|
$baName = $bAttr.GetAttribute("name")
|
||||||
|
if ($baName) { $baseAttrNames[$baName] = $true }
|
||||||
|
}
|
||||||
|
foreach ($bCmd in $baseFormNode.SelectNodes("f:Commands/f:Command", $bfNs)) {
|
||||||
|
$bcName = $bCmd.GetAttribute("name")
|
||||||
|
if ($bcName) { $baseCmdNames[$bcName] = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
$idWarnCount = 0
|
||||||
|
foreach ($attr in $attrNodes) {
|
||||||
|
$aName = $attr.GetAttribute("name")
|
||||||
|
$aId = $attr.GetAttribute("id")
|
||||||
|
if ($aName -and -not $baseAttrNames.ContainsKey($aName) -and $aId) {
|
||||||
|
try {
|
||||||
|
$intId = [int]$aId
|
||||||
|
if ($intId -lt 1000000) {
|
||||||
|
Report-Warn "Attribute '$aName' (id=$aId): extension-added attribute has id < 1000000"
|
||||||
|
$idWarnCount++
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($cmd in $cmdNodes) {
|
||||||
|
$cName = $cmd.GetAttribute("name")
|
||||||
|
$cId = $cmd.GetAttribute("id")
|
||||||
|
if ($cName -and -not $baseCmdNames.ContainsKey($cName) -and $cId) {
|
||||||
|
try {
|
||||||
|
$intId = [int]$cId
|
||||||
|
if ($intId -lt 1000000) {
|
||||||
|
Report-Warn "Command '$cName' (id=$cId): extension-added command has id < 1000000"
|
||||||
|
$idWarnCount++
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $stopped -and $idWarnCount -eq 0) {
|
||||||
|
$extAttrCount = ($attrNodes | Where-Object { -not $baseAttrNames.ContainsKey($_.GetAttribute("name")) }).Count
|
||||||
|
$extCmdCount = ($cmdNodes | Where-Object { -not $baseCmdNames.ContainsKey($_.GetAttribute("name")) }).Count
|
||||||
|
if (($extAttrCount + $extCmdCount) -gt 0) {
|
||||||
|
Report-OK "Extension ID ranges: $extAttrCount attr(s), $extCmdCount cmd(s) — all >= 1000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check callType without BaseForm (structural warning)
|
||||||
|
if (-not $stopped -and -not $isExtension) {
|
||||||
|
$callTypeWithoutBase = $false
|
||||||
|
$feNode = $root.SelectSingleNode("f:Events", $nsMgr)
|
||||||
|
if ($feNode) {
|
||||||
|
foreach ($evt in $feNode.SelectNodes("f:Event", $nsMgr)) {
|
||||||
|
if ($evt.GetAttribute("callType")) { $callTypeWithoutBase = $true; break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $callTypeWithoutBase) {
|
||||||
|
foreach ($cmd in $cmdNodes) {
|
||||||
|
foreach ($action in $cmd.SelectNodes("f:Action", $nsMgr)) {
|
||||||
|
if ($action.GetAttribute("callType")) { $callTypeWithoutBase = $true; break }
|
||||||
|
}
|
||||||
|
if ($callTypeWithoutBase) { break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($callTypeWithoutBase) {
|
||||||
|
Report-Warn "callType attributes found but no BaseForm — possible incorrect structure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# --- Summary ---
|
# --- Summary ---
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|||||||
Reference in New Issue
Block a user