Files
cc-1c-skills/.opencode/skills/epf-validate/scripts/epf-validate.ps1
T
2026-06-04 09:28:01 +00:00

843 lines
25 KiB
PowerShell

# epf-validate v1.2 — Validate 1C external data processor / report structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
param(
[Parameter(Mandatory)]
[Alias('Path')]
[string]$ObjectPath,
[switch]$Detailed,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ObjectPath)) {
$ObjectPath = Join-Path (Get-Location).Path $ObjectPath
}
if (Test-Path $ObjectPath -PathType Container) {
$dirName = Split-Path $ObjectPath -Leaf
$candidate = Join-Path $ObjectPath "$dirName.xml"
$sibling = Join-Path (Split-Path $ObjectPath) "$dirName.xml"
if (Test-Path $candidate) {
$ObjectPath = $candidate
} elseif (Test-Path $sibling) {
$ObjectPath = $sibling
} else {
$xmlFiles = @(Get-ChildItem $ObjectPath -Filter "*.xml" -File | Select-Object -First 1)
if ($xmlFiles.Count -gt 0) {
$ObjectPath = $xmlFiles[0].FullName
} else {
Write-Host "[ERROR] No XML file found in directory: $ObjectPath"
exit 1
}
}
}
# File not found — check Dir/Name/Name.xml → Dir/Name.xml
if (-not (Test-Path $ObjectPath)) {
$fileName = [System.IO.Path]::GetFileNameWithoutExtension($ObjectPath)
$parentDir = Split-Path $ObjectPath
$parentDirName = Split-Path $parentDir -Leaf
if ($fileName -eq $parentDirName) {
$candidate = Join-Path (Split-Path $parentDir) "$fileName.xml"
if (Test-Path $candidate) { $ObjectPath = $candidate }
}
}
if (-not (Test-Path $ObjectPath)) {
Write-Host "[ERROR] File not found: $ObjectPath"
exit 1
}
$resolvedPath = (Resolve-Path $ObjectPath).Path
$srcDir = Split-Path $resolvedPath -Parent
# --- Output infrastructure ---
$script:errors = 0
$script:warnings = 0
$script:okCount = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
function Out-Line {
param([string]$msg)
$script:output.AppendLine($msg) | Out-Null
}
function Report-OK {
param([string]$msg)
$script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
}
function Report-Error {
param([string]$msg)
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) {
$script:stopped = $true
}
}
function Report-Warn {
param([string]$msg)
$script:warnings++
Out-Line "[WARN] $msg"
}
$finalize = {
$checks = $script:okCount + $script:errors + $script:warnings
if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: $shortType.$objName ($checks checks) ==="
} else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
Write-Host $result
if ($OutFile) {
$utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
}
# --- Reference tables ---
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
$classIds = @{
"ExternalDataProcessor" = "c3831ec8-d8d5-4f93-8a22-f9bfae07327f"
"ExternalReport" = "e41aff26-25cf-4bb6-b6c1-3f478a75f374"
}
$allowedChildTypes = @("Attribute","TabularSection","Form","Template","Command")
# Expected order of child types in ChildObjects
$childTypeOrder = @{
"Attribute" = 0
"TabularSection" = 1
"Form" = 2
"Template" = 3
"Command" = 4
}
$validPropertyValues = @{
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
}
# --- 1. Parse XML ---
Out-Line ""
$xmlDoc = $null
try {
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $false
$xmlDoc.Load($resolvedPath)
} catch {
Out-Line "=== Validation: (parse failed) ==="
Out-Line ""
Report-Error "1. XML parse failed: $($_.Exception.Message)"
& $finalize
exit 1
}
# --- Register namespaces ---
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("app", "http://v8.1c.ru/8.2/managed-application/core")
$root = $xmlDoc.DocumentElement
# --- Check 1: Root structure ---
$check1Ok = $true
if ($root.LocalName -ne "MetaDataObject") {
Report-Error "1. Root element is '$($root.LocalName)', expected 'MetaDataObject'"
& $finalize
exit 1
}
$expectedNs = "http://v8.1c.ru/8.3/MDClasses"
if ($root.NamespaceURI -ne $expectedNs) {
Report-Error "1. Root namespace is '$($root.NamespaceURI)', expected '$expectedNs'"
$check1Ok = $false
}
$version = $root.GetAttribute("version")
if (-not $version) {
Report-Warn "1. Missing version attribute on MetaDataObject"
} elseif ($version -ne "2.17" -and $version -ne "2.20" -and $version -ne "2.21") {
Report-Warn "1. Unusual version '$version' (expected 2.17, 2.20 or 2.21)"
}
# Detect type: ExternalDataProcessor or ExternalReport
$typeNode = $null
$mdType = ""
$childElements = @()
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.NamespaceURI -eq $expectedNs) {
$childElements += $child
}
}
if ($childElements.Count -eq 0) {
Report-Error "1. No metadata type element found inside MetaDataObject"
& $finalize
exit 1
} elseif ($childElements.Count -gt 1) {
Report-Error "1. Multiple type elements found: $($childElements | ForEach-Object { $_.LocalName })"
$check1Ok = $false
}
$typeNode = $childElements[0]
$mdType = $typeNode.LocalName
if ($mdType -ne "ExternalDataProcessor" -and $mdType -ne "ExternalReport") {
Report-Error "1. Unexpected type '$mdType' (expected ExternalDataProcessor or ExternalReport)"
& $finalize
exit 1
}
$typeUuid = $typeNode.GetAttribute("uuid")
if (-not $typeUuid) {
Report-Error "1. Missing uuid on <$mdType>"
$check1Ok = $false
} elseif ($typeUuid -notmatch $guidPattern) {
Report-Error "1. Invalid uuid '$typeUuid' on <$mdType>"
$check1Ok = $false
}
# Get object name
$propsNode = $typeNode.SelectSingleNode("md:Properties", $ns)
$nameNode = if ($propsNode) { $propsNode.SelectSingleNode("md:Name", $ns) } else { $null }
$objName = if ($nameNode -and $nameNode.InnerText) { $nameNode.InnerText } else { "(unknown)" }
$shortType = if ($mdType -eq "ExternalDataProcessor") { "EPF" } else { "ERF" }
$script:output.Insert(0, "=== Validation: $shortType.$objName ===$([Environment]::NewLine)") | Out-Null
if ($check1Ok) {
Report-OK "1. Root structure: MetaDataObject/$mdType, version $version"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 2: InternalInfo ---
$internalInfo = $typeNode.SelectSingleNode("md:InternalInfo", $ns)
if (-not $internalInfo) {
Report-Error "2. InternalInfo block missing"
} else {
$check2Ok = $true
# ContainedObject / ClassId
$containedObj = $internalInfo.SelectSingleNode("xr:ContainedObject", $ns)
if (-not $containedObj) {
Report-Error "2. InternalInfo: missing xr:ContainedObject"
$check2Ok = $false
} else {
$classIdNode = $containedObj.SelectSingleNode("xr:ClassId", $ns)
$objectIdNode = $containedObj.SelectSingleNode("xr:ObjectId", $ns)
$expectedClassId = $classIds[$mdType]
if (-not $classIdNode -or -not $classIdNode.InnerText) {
Report-Error "2. Missing ClassId in ContainedObject"
$check2Ok = $false
} elseif ($classIdNode.InnerText -ne $expectedClassId) {
Report-Error "2. ClassId is '$($classIdNode.InnerText)', expected '$expectedClassId' for $mdType"
$check2Ok = $false
}
if ($objectIdNode -and $objectIdNode.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid ObjectId UUID"
$check2Ok = $false
}
}
# GeneratedType — expect exactly 1 with category "Object"
$genTypes = $internalInfo.SelectNodes("xr:GeneratedType", $ns)
if ($genTypes.Count -eq 0) {
Report-Error "2. No GeneratedType entries found"
$check2Ok = $false
} else {
foreach ($gt in $genTypes) {
$gtName = $gt.GetAttribute("name")
$gtCategory = $gt.GetAttribute("category")
if ($gtCategory -ne "Object") {
Report-Warn "2. Unexpected GeneratedType category '$gtCategory' (expected 'Object')"
}
# Name format: ExternalDataProcessorObject.Name or ExternalReportObject.Name
$expectedPrefix = "${mdType}Object."
if ($gtName -and $objName -ne "(unknown)" -and -not $gtName.StartsWith($expectedPrefix)) {
Report-Warn "2. GeneratedType name '$gtName' does not start with '$expectedPrefix'"
}
$typeId = $gt.SelectSingleNode("xr:TypeId", $ns)
$valueId = $gt.SelectSingleNode("xr:ValueId", $ns)
if ($typeId -and $typeId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid TypeId UUID in GeneratedType"
$check2Ok = $false
}
if ($valueId -and $valueId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid ValueId UUID in GeneratedType"
$check2Ok = $false
}
}
}
if ($check2Ok) {
Report-OK "2. InternalInfo: ClassId correct, $($genTypes.Count) GeneratedType"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 3: Properties ---
if (-not $propsNode) {
Report-Error "3. Properties block missing"
} else {
$check3Ok = $true
# Name
if (-not $nameNode -or -not $nameNode.InnerText) {
Report-Error "3. Properties: Name is missing or empty"
$check3Ok = $false
} else {
$nameVal = $nameNode.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "3. Properties: Name '$nameVal' is not a valid 1C identifier"
$check3Ok = $false
}
if ($nameVal.Length -gt 80) {
Report-Warn "3. Properties: Name '$nameVal' exceeds 80 characters ($($nameVal.Length))"
}
}
# Synonym
$synNode = $propsNode.SelectSingleNode("md:Synonym", $ns)
$synPresent = $false
if ($synNode) {
$synItem = $synNode.SelectSingleNode("v8:item", $ns)
if ($synItem) {
$synContent = $synItem.SelectSingleNode("v8:content", $ns)
if ($synContent -and $synContent.InnerText) {
$synPresent = $true
}
}
}
# DefaultForm cross-reference (collected now, checked after ChildObjects)
$defaultFormNode = $propsNode.SelectSingleNode("md:DefaultForm", $ns)
$defaultFormVal = if ($defaultFormNode -and $defaultFormNode.InnerText.Trim()) { $defaultFormNode.InnerText.Trim() } else { "" }
# AuxiliaryForm cross-reference
$auxFormNode = $propsNode.SelectSingleNode("md:AuxiliaryForm", $ns)
$auxFormVal = if ($auxFormNode -and $auxFormNode.InnerText.Trim()) { $auxFormNode.InnerText.Trim() } else { "" }
# ERF-specific: MainDataCompositionSchema
$mainDCSVal = ""
if ($mdType -eq "ExternalReport") {
$mainDCSNode = $propsNode.SelectSingleNode("md:MainDataCompositionSchema", $ns)
$mainDCSVal = if ($mainDCSNode -and $mainDCSNode.InnerText.Trim()) { $mainDCSNode.InnerText.Trim() } else { "" }
}
if ($check3Ok) {
$synInfo = if ($synPresent) { "Synonym present" } else { "no Synonym" }
$extras = ""
if ($defaultFormVal) { $extras += ", DefaultForm set" }
if ($mainDCSVal) { $extras += ", MainDCS set" }
Report-OK "3. Properties: Name=`"$objName`", $synInfo$extras"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 4: ChildObjects — allowed types and ordering ---
$childObjNode = $typeNode.SelectSingleNode("md:ChildObjects", $ns)
$formNames = @()
$templateNames = @()
$allChildNames = @{}
if ($childObjNode) {
$check4Ok = $true
$childCounts = @{}
$lastOrder = -1
$orderOk = $true
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$childTag = $child.LocalName
if ($allowedChildTypes -notcontains $childTag) {
Report-Error "4. ChildObjects: disallowed element '$childTag'"
$check4Ok = $false
continue
}
if (-not $childCounts.ContainsKey($childTag)) {
$childCounts[$childTag] = 0
}
$childCounts[$childTag]++
# Check ordering
$thisOrder = $childTypeOrder[$childTag]
if ($thisOrder -lt $lastOrder -and $orderOk) {
Report-Warn "4. ChildObjects: '$childTag' appears after higher-order elements (expected: Attribute, TabularSection, Form, Template, Command)"
$orderOk = $false
}
$lastOrder = $thisOrder
# Collect Form and Template names (simple text content)
if ($childTag -eq "Form") {
$formNames += $child.InnerText.Trim()
} elseif ($childTag -eq "Template") {
$templateNames += $child.InnerText.Trim()
}
}
if ($check4Ok) {
$summary = ($childCounts.GetEnumerator() | Sort-Object { $childTypeOrder[$_.Name] } | ForEach-Object { "$($_.Name)($($_.Value))" }) -join ", "
if ($summary) {
Report-OK "4. ChildObjects: $summary"
} else {
Report-OK "4. ChildObjects: empty"
}
}
} else {
Report-OK "4. ChildObjects: absent"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 5: DefaultForm / MainDCS cross-references ---
$check5Ok = $true
if ($defaultFormVal) {
# Format: ExternalDataProcessor.Name.Form.FormName or ExternalReport.Name.Form.FormName
$expectedPrefix = "$mdType.$objName.Form."
if ($defaultFormVal.StartsWith($expectedPrefix)) {
$refFormName = $defaultFormVal.Substring($expectedPrefix.Length)
if ($formNames -notcontains $refFormName) {
Report-Error "5. DefaultForm references '$refFormName', but no such Form in ChildObjects"
$check5Ok = $false
}
} else {
Report-Warn "5. DefaultForm value '$defaultFormVal' has unexpected prefix (expected '$expectedPrefix...')"
}
}
if ($auxFormVal) {
$expectedPrefix = "$mdType.$objName.Form."
if ($auxFormVal.StartsWith($expectedPrefix)) {
$refFormName = $auxFormVal.Substring($expectedPrefix.Length)
if ($formNames -notcontains $refFormName) {
Report-Error "5. AuxiliaryForm references '$refFormName', but no such Form in ChildObjects"
$check5Ok = $false
}
}
}
if ($mainDCSVal -and $mdType -eq "ExternalReport") {
$expectedPrefix = "ExternalReport.$objName.Template."
if ($mainDCSVal.StartsWith($expectedPrefix)) {
$refTplName = $mainDCSVal.Substring($expectedPrefix.Length)
if ($templateNames -notcontains $refTplName) {
Report-Error "5. MainDataCompositionSchema references '$refTplName', but no such Template in ChildObjects"
$check5Ok = $false
}
} else {
Report-Warn "5. MainDataCompositionSchema value '$mainDCSVal' has unexpected prefix"
}
}
if ($check5Ok) {
$refs = @()
if ($defaultFormVal) { $refs += "DefaultForm" }
if ($auxFormVal) { $refs += "AuxiliaryForm" }
if ($mainDCSVal) { $refs += "MainDCS" }
if ($refs.Count -gt 0) {
Report-OK "5. Cross-references: $($refs -join ', ') valid"
} else {
Report-OK "5. Cross-references: none to check"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 6: Attributes — UUID, Name, Type ---
function Check-Attribute {
param(
[System.Xml.XmlNode]$node,
[string]$context
)
$uuid = $node.GetAttribute("uuid")
if (-not $uuid) {
Report-Error "6. $context Attribute missing uuid"
return $false
} elseif ($uuid -notmatch $guidPattern) {
Report-Error "6. $context Attribute has invalid uuid '$uuid'"
return $false
}
$elProps = $node.SelectSingleNode("md:Properties", $ns)
if (-not $elProps) {
Report-Error "6. $context Attribute (uuid=$uuid) missing Properties"
return $false
}
$elName = $elProps.SelectSingleNode("md:Name", $ns)
if (-not $elName -or -not $elName.InnerText) {
Report-Error "6. $context Attribute (uuid=$uuid) missing or empty Name"
return $false
}
$nameVal = $elName.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "6. $context Attribute '$nameVal' has invalid identifier"
return $false
}
$typeEl = $elProps.SelectSingleNode("md:Type", $ns)
if (-not $typeEl) {
Report-Error "6. $context Attribute '$nameVal' missing Type block"
return $false
}
$v8Types = $typeEl.SelectNodes("v8:Type", $ns)
$v8TypeSets = $typeEl.SelectNodes("v8:TypeSet", $ns)
if ($v8Types.Count -eq 0 -and $v8TypeSets.Count -eq 0) {
Report-Error "6. $context Attribute '$nameVal' Type block has no v8:Type or v8:TypeSet"
return $false
}
return $true
}
if ($childObjNode) {
$attrs = $childObjNode.SelectNodes("md:Attribute", $ns)
$check6Ok = $true
$attrCount = 0
foreach ($attr in $attrs) {
if ($script:stopped) { break }
$ok = Check-Attribute -node $attr -context ""
if (-not $ok) { $check6Ok = $false }
$attrCount++
# Collect name for uniqueness
$ap = $attr.SelectSingleNode("md:Properties/md:Name", $ns)
if ($ap -and $ap.InnerText) {
$allChildNames["Attr:$($ap.InnerText)"] = $ap.InnerText
}
}
if ($attrCount -gt 0) {
if ($check6Ok) {
Report-OK "6. Attributes: $attrCount checked (UUID, Name, Type)"
}
} else {
Report-OK "6. Attributes: none"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 7: TabularSections ---
if ($childObjNode) {
$tsSections = $childObjNode.SelectNodes("md:TabularSection", $ns)
if ($tsSections.Count -gt 0) {
$check7Ok = $true
$tsCount = 0
$tsAttrTotal = 0
foreach ($ts in $tsSections) {
if ($script:stopped) { break }
$tsCount++
$tsUuid = $ts.GetAttribute("uuid")
if (-not $tsUuid -or $tsUuid -notmatch $guidPattern) {
Report-Error "7. TabularSection #${tsCount}: invalid or missing uuid"
$check7Ok = $false
}
$tsProps = $ts.SelectSingleNode("md:Properties", $ns)
$tsNameNode = if ($tsProps) { $tsProps.SelectSingleNode("md:Name", $ns) } else { $null }
$tsName = if ($tsNameNode -and $tsNameNode.InnerText) { $tsNameNode.InnerText } else { "(unnamed)" }
if (-not $tsNameNode -or -not $tsNameNode.InnerText) {
Report-Error "7. TabularSection #${tsCount}: missing or empty Name"
$check7Ok = $false
} elseif ($tsName -notmatch $identPattern) {
Report-Error "7. TabularSection '$tsName': invalid identifier"
$check7Ok = $false
}
$allChildNames["TS:$tsName"] = $tsName
# InternalInfo — expect 2 GeneratedType
$tsIntInfo = $ts.SelectSingleNode("md:InternalInfo", $ns)
if ($tsIntInfo) {
$tsGens = $tsIntInfo.SelectNodes("xr:GeneratedType", $ns)
if ($tsGens.Count -lt 2) {
Report-Warn "7. TabularSection '$tsName': expected 2 GeneratedType, found $($tsGens.Count)"
}
}
# Inner attributes
$tsChildObj = $ts.SelectSingleNode("md:ChildObjects", $ns)
if ($tsChildObj) {
$tsAttrs = $tsChildObj.SelectNodes("md:Attribute", $ns)
$tsAttrNames = @{}
foreach ($ta in $tsAttrs) {
$taOk = Check-Attribute -node $ta -context "TabularSection '$tsName'."
if (-not $taOk) { $check7Ok = $false }
$tsAttrTotal++
$taProps = $ta.SelectSingleNode("md:Properties/md:Name", $ns)
if ($taProps -and $taProps.InnerText) {
if ($tsAttrNames.ContainsKey($taProps.InnerText)) {
Report-Error "7. Duplicate attribute '$($taProps.InnerText)' in TabularSection '$tsName'"
$check7Ok = $false
} else {
$tsAttrNames[$taProps.InnerText] = $true
}
}
}
}
}
if ($check7Ok) {
Report-OK "7. TabularSections: $tsCount sections, $tsAttrTotal inner attributes"
}
} else {
Report-OK "7. TabularSections: none"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 8: Name uniqueness ---
$check8Ok = $true
# Collect all names: attributes + tabular sections + forms + templates + commands
$allNames = @{}
if ($childObjNode) {
$nameKinds = @(
@{ XPath = "md:Attribute"; Kind = "Attribute" },
@{ XPath = "md:TabularSection"; Kind = "TabularSection" },
@{ XPath = "md:Command"; Kind = "Command" }
)
foreach ($nk in $nameKinds) {
$nodes = $childObjNode.SelectNodes($nk.XPath, $ns)
foreach ($node in $nodes) {
$np = $node.SelectSingleNode("md:Properties/md:Name", $ns)
if ($np -and $np.InnerText) {
$nameVal = $np.InnerText
$key = "$($nk.Kind):$nameVal"
if ($allNames.ContainsKey($nameVal)) {
Report-Error "8. Duplicate name '$nameVal' ($($nk.Kind) conflicts with $($allNames[$nameVal]))"
$check8Ok = $false
} else {
$allNames[$nameVal] = $nk.Kind
}
}
}
}
# Forms and Templates are simple text nodes
foreach ($fn in $formNames) {
if ($allNames.ContainsKey($fn)) {
Report-Error "8. Duplicate name '$fn' (Form conflicts with $($allNames[$fn]))"
$check8Ok = $false
} else {
$allNames[$fn] = "Form"
}
}
foreach ($tn in $templateNames) {
if ($allNames.ContainsKey($tn)) {
Report-Error "8. Duplicate name '$tn' (Template conflicts with $($allNames[$tn]))"
$check8Ok = $false
} else {
$allNames[$tn] = "Template"
}
}
}
if ($check8Ok) {
Report-OK "8. Name uniqueness: $($allNames.Count) names, all unique"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 9: File existence (forms and templates on disk) ---
$check9Ok = $true
$filesChecked = 0
# Object directory: same level as root XML, named after the object
$objDir = Join-Path $srcDir $objName
foreach ($fn in $formNames) {
# FormName.xml — form descriptor
$formMetaXml = Join-Path (Join-Path $objDir "Forms") "$fn.xml"
if (-not (Test-Path $formMetaXml)) {
Report-Error "9. Missing form descriptor: Forms/$fn.xml"
$check9Ok = $false
} else {
$filesChecked++
}
# FormName/Ext/Form.xml — form layout
$formXml = Join-Path (Join-Path (Join-Path (Join-Path $objDir "Forms") $fn) "Ext") "Form.xml"
if (-not (Test-Path $formXml)) {
Report-Error "9. Missing form layout: Forms/$fn/Ext/Form.xml"
$check9Ok = $false
} else {
$filesChecked++
}
}
foreach ($tn in $templateNames) {
# TemplateName.xml — template descriptor
$tplMetaXml = Join-Path (Join-Path $objDir "Templates") "$tn.xml"
if (-not (Test-Path $tplMetaXml)) {
Report-Error "9. Missing template descriptor: Templates/$tn.xml"
$check9Ok = $false
} else {
$filesChecked++
}
# TemplateName/Ext/Template.* — template content (extension varies)
$tplExtDir = Join-Path (Join-Path (Join-Path $objDir "Templates") $tn) "Ext"
if (Test-Path $tplExtDir) {
$tplFiles = @(Get-ChildItem $tplExtDir -Filter "Template.*" -File)
if ($tplFiles.Count -eq 0) {
Report-Error "9. Missing template content: Templates/$tn/Ext/Template.*"
$check9Ok = $false
} else {
$filesChecked++
}
} else {
Report-Error "9. Missing template Ext directory: Templates/$tn/Ext/"
$check9Ok = $false
}
}
# ObjectModule.bsl
$objModule = Join-Path (Join-Path $objDir "Ext") "ObjectModule.bsl"
if (Test-Path $objModule) {
$filesChecked++
}
if ($check9Ok) {
if ($filesChecked -gt 0) {
Report-OK "9. File existence: $filesChecked files verified"
} else {
Report-OK "9. File existence: no forms/templates to check"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 10: Form descriptors structure ---
$check10Ok = $true
$formsChecked = 0
foreach ($fn in $formNames) {
$formMetaXml = Join-Path (Join-Path $objDir "Forms") "$fn.xml"
if (-not (Test-Path $formMetaXml)) { continue }
try {
$fDoc = New-Object System.Xml.XmlDocument
$fDoc.PreserveWhitespace = $false
$fDoc.Load($formMetaXml)
$fRoot = $fDoc.DocumentElement
if ($fRoot.LocalName -ne "MetaDataObject") {
Report-Error "10. Form '$fn': root element is '$($fRoot.LocalName)', expected 'MetaDataObject'"
$check10Ok = $false
continue
}
$fTypeNode = $fRoot.SelectSingleNode("md:Form", $ns)
if (-not $fTypeNode) {
Report-Error "10. Form '$fn': missing <Form> element"
$check10Ok = $false
continue
}
$fUuid = $fTypeNode.GetAttribute("uuid")
if (-not $fUuid -or $fUuid -notmatch $guidPattern) {
Report-Error "10. Form '$fn': invalid or missing uuid"
$check10Ok = $false
}
$fProps = $fTypeNode.SelectSingleNode("md:Properties", $ns)
if ($fProps) {
$fName = $fProps.SelectSingleNode("md:Name", $ns)
if ($fName -and $fName.InnerText -ne $fn) {
Report-Error "10. Form '$fn': Name in descriptor is '$($fName.InnerText)', expected '$fn'"
$check10Ok = $false
}
# FormType should be Managed
$fType = $fProps.SelectSingleNode("md:FormType", $ns)
if ($fType -and $fType.InnerText -ne "Managed") {
Report-Warn "10. Form '$fn': FormType is '$($fType.InnerText)' (expected 'Managed')"
}
}
$formsChecked++
} catch {
Report-Error "10. Form '$fn': XML parse error: $($_.Exception.Message)"
$check10Ok = $false
}
}
if ($check10Ok) {
if ($formsChecked -gt 0) {
Report-OK "10. Form descriptors: $formsChecked checked"
} else {
Report-OK "10. Form descriptors: none to check"
}
}
# --- Final output ---
& $finalize
if ($script:errors -gt 0) {
exit 1
}
exit 0