mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-12 08:54:57 +03:00
Add /meta-validate skill: structural validation of 1C metadata XML
11 check categories covering all 23 metadata types: root structure, InternalInfo, properties, enum values, StandardAttributes, ChildObjects, child elements (UUID/Name/Type), name uniqueness, TabularSections, cross-properties, HTTPService/WebService nested structure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
---
|
||||
name: meta-validate
|
||||
description: Валидация структурной корректности объекта метаданных 1С (Справочник, Документ, Регистр, Перечисление и ещё 19 типов)
|
||||
argument-hint: <ObjectPath> [-MaxErrors 30]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /meta-validate — валидация объекта метаданных 1С
|
||||
|
||||
Проверяет XML объекта метаданных из выгрузки конфигурации на структурные ошибки: корневую структуру, InternalInfo, свойства, допустимые значения, StandardAttributes, ChildObjects, уникальность имён, табличные части, кросс-свойства, вложенные структуры HTTP/Web-сервисов.
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/meta-validate <ObjectPath>
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|------------|:------------:|--------------|-------------------------------------------------|
|
||||
| ObjectPath | да | — | Путь к XML-файлу или каталогу объекта |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude\skills\meta-validate\scripts\meta-validate.ps1 -ObjectPath "<путь>"
|
||||
```
|
||||
|
||||
## Поддерживаемые типы (23)
|
||||
|
||||
**Ссылочные:** Catalog, Document, Enum, ExchangePlan, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task
|
||||
**Регистры:** InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister
|
||||
**Отчёты/Обработки:** Report, DataProcessor
|
||||
**Сервисные:** CommonModule, ScheduledJob, EventSubscription, HTTPService, WebService
|
||||
**Прочие:** Constant, DocumentJournal, DefinedType
|
||||
|
||||
## Выполняемые проверки
|
||||
|
||||
| # | Проверка | Серьёзность |
|
||||
|----|------------------------------------------|--------------|
|
||||
| 1 | XML well-formedness + root structure | ERROR |
|
||||
| 2 | InternalInfo / GeneratedType | ERROR / WARN |
|
||||
| 3 | Properties — Name, Synonym | ERROR / WARN |
|
||||
| 4 | Properties — enum-значения свойств | ERROR |
|
||||
| 5 | StandardAttributes | ERROR / WARN |
|
||||
| 6 | ChildObjects — допустимые элементы | ERROR |
|
||||
| 7 | Attributes/Dimensions/Resources — UUID, Name, Type | ERROR |
|
||||
| 8 | Уникальность имён | ERROR |
|
||||
| 9 | TabularSections — внутренняя структура | ERROR / WARN |
|
||||
| 10 | Кросс-свойства | ERROR / WARN |
|
||||
| 11 | HTTPService/WebService — вложенная структура | ERROR |
|
||||
|
||||
## Вывод
|
||||
|
||||
```
|
||||
=== Validation: Catalog.Номенклатура ===
|
||||
|
||||
[OK] 1. Root structure: MetaDataObject/Catalog, version 2.17
|
||||
[OK] 2. InternalInfo: 5 GeneratedType (Object, Ref, Selection, List, Manager)
|
||||
[OK] 3. Properties: Name="Номенклатура", Synonym present
|
||||
[OK] 4. Property values: 12 enum properties checked
|
||||
[ERROR] 5. StandardAttributes: missing "PredefinedDataName"
|
||||
[OK] 6. ChildObjects types: Attribute(15), TabularSection(3), Form(4)
|
||||
[OK] 7. Attributes/Dimensions: all valid
|
||||
[WARN] 8. Name uniqueness: duplicate attribute "Комментарий" at positions 5, 12
|
||||
[OK] 9. TabularSections: 3 sections, structure valid
|
||||
[OK] 10. Cross-property consistency
|
||||
[OK] 11. N/A (not HTTPService/WebService)
|
||||
---
|
||||
Errors: 1, Warnings: 1
|
||||
```
|
||||
|
||||
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Справочник из выгрузки конфигурации
|
||||
... -ObjectPath upload/acc_8.3.24/Catalogs/Банки/Банки.xml
|
||||
|
||||
# Авторезолв из директории
|
||||
... -ObjectPath upload/acc_8.3.24/Documents/АвансовыйОтчет
|
||||
|
||||
# С лимитом ошибок
|
||||
... -ObjectPath Catalogs/Номенклатура.xml -MaxErrors 10
|
||||
|
||||
# С записью в файл
|
||||
... -ObjectPath Catalogs/Номенклатура.xml -OutFile result.txt
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/meta-compile <JsonPath> <OutputDir> — генерация XML
|
||||
/meta-validate <OutputDir>/<Type>/<Name>.xml — проверка результата
|
||||
/meta-info <OutputDir>/<Type>/<Name>.xml — визуальная сводка
|
||||
```
|
||||
|
||||
## Когда использовать
|
||||
|
||||
- **После `/meta-compile`**: проверить корректность сгенерированного XML
|
||||
- **После ручного редактирования**: убедиться что структура не нарушена
|
||||
- **После merge/импорта**: выявить конфликты и битые ссылки
|
||||
- **При отладке**: найти структурные ошибки до сборки EPF
|
||||
@@ -0,0 +1,984 @@
|
||||
# meta-validate v1.0 — Validate 1C metadata object structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ObjectPath,
|
||||
|
||||
[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"
|
||||
if (Test-Path $candidate) {
|
||||
$ObjectPath = $candidate
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ObjectPath)) {
|
||||
Write-Host "[ERROR] File not found: $ObjectPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$resolvedPath = (Resolve-Path $ObjectPath).Path
|
||||
|
||||
# --- Output infrastructure ---
|
||||
|
||||
$script:errors = 0
|
||||
$script:warnings = 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)
|
||||
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 = {
|
||||
Out-Line ""
|
||||
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ==="
|
||||
|
||||
$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_]*$'
|
||||
|
||||
$validTypes = @(
|
||||
"Catalog","Document","Enum","Constant",
|
||||
"InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister",
|
||||
"ChartOfAccounts","ChartOfCharacteristicTypes","ChartOfCalculationTypes",
|
||||
"BusinessProcess","Task","ExchangePlan","DocumentJournal",
|
||||
"Report","DataProcessor",
|
||||
"CommonModule","ScheduledJob","EventSubscription",
|
||||
"HTTPService","WebService","DefinedType"
|
||||
)
|
||||
|
||||
# GeneratedType categories by type
|
||||
$generatedTypeCategories = @{
|
||||
"Catalog" = @("Object","Ref","Selection","List","Manager")
|
||||
"Document" = @("Object","Ref","Selection","List","Manager")
|
||||
"Enum" = @("Ref","Manager","List")
|
||||
"Constant" = @("Manager","ValueManager","ValueKey")
|
||||
"InformationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","RecordManager")
|
||||
"AccumulationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey")
|
||||
"AccountingRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","ExtDimensions")
|
||||
"CalculationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey")
|
||||
"ChartOfAccounts" = @("Object","Ref","Selection","List","Manager","ExtDimensionTypes","ExtDimensionTypesRow")
|
||||
"ChartOfCharacteristicTypes" = @("Object","Ref","Selection","List","Manager")
|
||||
"ChartOfCalculationTypes" = @("Object","Ref","Selection","List","Manager","DisplacingCalculationTypes","BaseCalculationTypes","LeadingCalculationTypes")
|
||||
"BusinessProcess" = @("Object","Ref","Selection","List","Manager")
|
||||
"Task" = @("Object","Ref","Selection","List","Manager")
|
||||
"ExchangePlan" = @("Object","Ref","Selection","List","Manager")
|
||||
"DocumentJournal" = @("Selection","List","Manager")
|
||||
"Report" = @("Object")
|
||||
"DataProcessor" = @("Object")
|
||||
}
|
||||
|
||||
# Types that have NO InternalInfo / GeneratedType
|
||||
$typesWithoutInternalInfo = @("CommonModule","ScheduledJob","EventSubscription","DefinedType")
|
||||
|
||||
# StandardAttributes by type
|
||||
$standardAttributesByType = @{
|
||||
"Catalog" = @("PredefinedDataName","Predefined","Ref","DeletionMark","IsFolder","Owner","Parent","Description","Code")
|
||||
"Document" = @("Posted","Ref","DeletionMark","Date","Number")
|
||||
"Enum" = @("Order","Ref")
|
||||
"InformationRegister" = @("Active","LineNumber","Recorder","Period")
|
||||
"AccumulationRegister" = @("Active","LineNumber","Recorder","Period","RecordType")
|
||||
"AccountingRegister" = @("Active","Period","Recorder","LineNumber","Account","PeriodAdjustment")
|
||||
"CalculationRegister" = @("Active","Recorder","LineNumber","RegistrationPeriod","CalculationType","ReversingEntry")
|
||||
"ChartOfAccounts" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","Order","Type","OffBalance")
|
||||
"ChartOfCharacteristicTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","ValueType")
|
||||
"ChartOfCalculationTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","ActionPeriodIsBasic")
|
||||
"BusinessProcess" = @("Ref","DeletionMark","Date","Number","Started","Completed","HeadTask")
|
||||
"Task" = @("Ref","DeletionMark","Date","Number","Executed","Description","RoutePoint","BusinessProcess")
|
||||
"ExchangePlan" = @("Ref","DeletionMark","Code","Description","ThisNode","SentNo","ReceivedNo")
|
||||
"DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number")
|
||||
}
|
||||
|
||||
# Types that have StandardAttributes block
|
||||
$typesWithStdAttrs = @(
|
||||
"Catalog","Document","Enum",
|
||||
"InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister",
|
||||
"ChartOfAccounts","ChartOfCharacteristicTypes","ChartOfCalculationTypes",
|
||||
"BusinessProcess","Task","ExchangePlan","DocumentJournal"
|
||||
)
|
||||
|
||||
# ChildObjects rules: what child element types are valid for each metadata type
|
||||
$childObjectRules = @{
|
||||
"Catalog" = @("Attribute","TabularSection","Form","Template","Command")
|
||||
"Document" = @("Attribute","TabularSection","Form","Template","Command")
|
||||
"ExchangePlan" = @("Attribute","TabularSection","Form","Template","Command")
|
||||
"ChartOfAccounts" = @("Attribute","TabularSection","Form","Template","Command","AccountingFlag","ExtDimensionAccountingFlag")
|
||||
"ChartOfCharacteristicTypes" = @("Attribute","TabularSection","Form","Template","Command")
|
||||
"ChartOfCalculationTypes" = @("Attribute","TabularSection","Form","Template","Command")
|
||||
"BusinessProcess" = @("Attribute","TabularSection","Form","Template","Command")
|
||||
"Task" = @("Attribute","TabularSection","Form","Template","Command","AddressingAttribute")
|
||||
"Report" = @("Attribute","TabularSection","Form","Template","Command")
|
||||
"DataProcessor" = @("Attribute","TabularSection","Form","Template","Command")
|
||||
"Enum" = @("EnumValue","Form","Template","Command")
|
||||
"InformationRegister" = @("Dimension","Resource","Attribute","Form","Template","Command")
|
||||
"AccumulationRegister" = @("Dimension","Resource","Attribute","Form","Template","Command")
|
||||
"AccountingRegister" = @("Dimension","Resource","Attribute","Form","Template","Command")
|
||||
"CalculationRegister" = @("Dimension","Resource","Attribute","Form","Template","Command","Recalculation")
|
||||
"DocumentJournal" = @("Column","Form","Template","Command")
|
||||
"HTTPService" = @("URLTemplate")
|
||||
"WebService" = @("Operation")
|
||||
"Constant" = @("Form")
|
||||
"DefinedType" = @()
|
||||
"CommonModule" = @()
|
||||
"ScheduledJob" = @()
|
||||
"EventSubscription" = @()
|
||||
}
|
||||
|
||||
# Valid enum property values
|
||||
$validPropertyValues = @{
|
||||
"CodeType" = @("String","Number")
|
||||
"CodeAllowedLength" = @("Variable","Fixed")
|
||||
"NumberType" = @("String","Number")
|
||||
"NumberAllowedLength" = @("Variable","Fixed")
|
||||
"Posting" = @("Allow","Deny")
|
||||
"RealTimePosting" = @("Allow","Deny")
|
||||
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
|
||||
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
|
||||
"DataLockControlMode" = @("Automatic","Managed")
|
||||
"FullTextSearch" = @("Use","DontUse")
|
||||
"DefaultPresentation" = @("AsDescription","AsCode")
|
||||
"HierarchyType" = @("HierarchyFoldersAndItems","HierarchyItemsOnly")
|
||||
"EditType" = @("InDialog","InList","BothWays")
|
||||
"WriteMode" = @("Independent","RecorderSubordinate")
|
||||
"InformationRegisterPeriodicity" = @("Nonperiodical","Second","Day","Month","Quarter","Year")
|
||||
"RegisterType" = @("Balance","Turnovers")
|
||||
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
|
||||
"ReuseSessions" = @("DontUse","AutoUse")
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
"DataHistory" = @("Use","DontUse")
|
||||
}
|
||||
|
||||
# --- 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
|
||||
}
|
||||
|
||||
# --- 2. 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("cfg", "http://v8.1c.ru/8.1/data/enterprise/current-config")
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# --- Check 1: Root structure ---
|
||||
|
||||
$check1Ok = $true
|
||||
|
||||
# Root must be MetaDataObject
|
||||
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 attribute
|
||||
$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") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
|
||||
}
|
||||
|
||||
# Detect type element — exactly one child element in md namespace
|
||||
$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 ($validTypes -notcontains $mdType) {
|
||||
Report-Error "1. Unrecognized metadata type: $mdType"
|
||||
& $finalize
|
||||
exit 1
|
||||
}
|
||||
|
||||
# UUID on type element
|
||||
$typeUuid = $typeNode.GetAttribute("uuid")
|
||||
if (-not $typeUuid) {
|
||||
Report-Error "1. Missing uuid on <$mdType> element"
|
||||
$check1Ok = $false
|
||||
} elseif ($typeUuid -notmatch $guidPattern) {
|
||||
Report-Error "1. Invalid uuid '$typeUuid' on <$mdType>"
|
||||
$check1Ok = $false
|
||||
}
|
||||
|
||||
# Get object name early for header
|
||||
$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)" }
|
||||
|
||||
# Now emit header
|
||||
$script:output.Insert(0, "=== Validation: $mdType.$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 ($typesWithoutInternalInfo -contains $mdType) {
|
||||
# These types should NOT have InternalInfo with GeneratedType
|
||||
if ($internalInfo) {
|
||||
$genTypes = $internalInfo.SelectNodes("xr:GeneratedType", $ns)
|
||||
if ($genTypes.Count -gt 0) {
|
||||
Report-Warn "2. InternalInfo: $mdType should not have GeneratedType entries, found $($genTypes.Count)"
|
||||
} else {
|
||||
Report-OK "2. InternalInfo: absent or empty (correct for $mdType)"
|
||||
}
|
||||
} else {
|
||||
Report-OK "2. InternalInfo: absent (correct for $mdType)"
|
||||
}
|
||||
} elseif ($generatedTypeCategories.ContainsKey($mdType)) {
|
||||
$expectedCategories = $generatedTypeCategories[$mdType]
|
||||
if (-not $internalInfo) {
|
||||
Report-Error "2. InternalInfo: missing (expected $($expectedCategories.Count) GeneratedType)"
|
||||
} else {
|
||||
$genTypes = $internalInfo.SelectNodes("xr:GeneratedType", $ns)
|
||||
$check2Ok = $true
|
||||
$foundCategories = @()
|
||||
|
||||
foreach ($gt in $genTypes) {
|
||||
$gtName = $gt.GetAttribute("name")
|
||||
$gtCategory = $gt.GetAttribute("category")
|
||||
$foundCategories += $gtCategory
|
||||
|
||||
# Validate name format: Prefix.ObjectName
|
||||
if ($gtName -and $objName -ne "(unknown)") {
|
||||
if (-not $gtName.EndsWith(".$objName")) {
|
||||
Report-Error "2. GeneratedType name '$gtName' does not end with '.$objName'"
|
||||
$check2Ok = $false
|
||||
}
|
||||
}
|
||||
|
||||
# Validate category
|
||||
if ($expectedCategories -notcontains $gtCategory) {
|
||||
Report-Warn "2. Unexpected GeneratedType category '$gtCategory' for $mdType"
|
||||
}
|
||||
|
||||
# Validate TypeId and ValueId UUIDs
|
||||
$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 '$gtCategory'"
|
||||
$check2Ok = $false
|
||||
}
|
||||
if ($valueId -and $valueId.InnerText -notmatch $guidPattern) {
|
||||
Report-Error "2. Invalid ValueId UUID in GeneratedType '$gtCategory'"
|
||||
$check2Ok = $false
|
||||
}
|
||||
}
|
||||
|
||||
# ExchangePlan: check for ThisNode
|
||||
if ($mdType -eq "ExchangePlan") {
|
||||
$thisNode = $internalInfo.SelectSingleNode("xr:ThisNode", $ns)
|
||||
if (-not $thisNode) {
|
||||
Report-Warn "2. ExchangePlan missing xr:ThisNode in InternalInfo"
|
||||
} elseif ($thisNode.InnerText -notmatch $guidPattern) {
|
||||
Report-Error "2. ExchangePlan xr:ThisNode has invalid UUID"
|
||||
$check2Ok = $false
|
||||
}
|
||||
}
|
||||
|
||||
# Check count mismatch
|
||||
$missingCats = @($expectedCategories | Where-Object { $foundCategories -notcontains $_ })
|
||||
if ($missingCats.Count -gt 0) {
|
||||
Report-Warn "2. Missing GeneratedType categories: $($missingCats -join ', ')"
|
||||
}
|
||||
|
||||
if ($check2Ok) {
|
||||
$catList = ($foundCategories | Sort-Object) -join ", "
|
||||
Report-OK "2. InternalInfo: $($genTypes.Count) GeneratedType ($catList)"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Report-OK "2. InternalInfo: N/A for $mdType"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 3: Properties — Name, Synonym ---
|
||||
|
||||
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' is longer than 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($check3Ok) {
|
||||
$synInfo = if ($synPresent) { "Synonym present" } else { "no Synonym" }
|
||||
Report-OK "3. Properties: Name=`"$objName`", $synInfo"
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 4: Property values — enum properties ---
|
||||
|
||||
if ($propsNode) {
|
||||
$enumChecked = 0
|
||||
$check4Ok = $true
|
||||
|
||||
foreach ($propName in $validPropertyValues.Keys) {
|
||||
$propNode = $propsNode.SelectSingleNode("md:$propName", $ns)
|
||||
if ($propNode -and $propNode.InnerText) {
|
||||
$val = $propNode.InnerText
|
||||
$allowed = $validPropertyValues[$propName]
|
||||
if ($allowed -notcontains $val) {
|
||||
Report-Error "4. Property '$propName' has invalid value '$val' (allowed: $($allowed -join ', '))"
|
||||
$check4Ok = $false
|
||||
}
|
||||
$enumChecked++
|
||||
}
|
||||
}
|
||||
|
||||
if ($check4Ok) {
|
||||
Report-OK "4. Property values: $enumChecked enum properties checked"
|
||||
}
|
||||
} else {
|
||||
Report-Warn "4. No Properties block to check"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 5: StandardAttributes ---
|
||||
|
||||
if ($typesWithStdAttrs -contains $mdType) {
|
||||
$stdAttrNode = $propsNode.SelectSingleNode("md:StandardAttributes", $ns)
|
||||
if (-not $stdAttrNode) {
|
||||
Report-Error "5. StandardAttributes block missing for $mdType"
|
||||
} else {
|
||||
$stdAttrs = $stdAttrNode.SelectNodes("xr:StandardAttribute", $ns)
|
||||
$expectedStdAttrs = $standardAttributesByType[$mdType]
|
||||
$check5Ok = $true
|
||||
|
||||
$foundNames = @()
|
||||
foreach ($sa in $stdAttrs) {
|
||||
$saName = $sa.GetAttribute("name")
|
||||
if ($saName) {
|
||||
$foundNames += $saName
|
||||
if ($expectedStdAttrs -notcontains $saName) {
|
||||
# AccountingRegister has dynamic ExtDimension{N}/ExtDimensionType{N} attrs
|
||||
$isDynamic = ($mdType -eq "AccountingRegister" -and ($saName -match '^ExtDimension\d+$' -or $saName -match '^ExtDimensionType\d+$'))
|
||||
if (-not $isDynamic) {
|
||||
Report-Warn "5. Unexpected StandardAttribute '$saName' for $mdType"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Report-Error "5. StandardAttribute without 'name' attribute"
|
||||
$check5Ok = $false
|
||||
}
|
||||
}
|
||||
|
||||
if ($expectedStdAttrs) {
|
||||
$missingAttrs = @($expectedStdAttrs | Where-Object { $foundNames -notcontains $_ })
|
||||
if ($missingAttrs.Count -gt 0) {
|
||||
Report-Warn "5. Missing StandardAttributes: $($missingAttrs -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
if ($check5Ok) {
|
||||
Report-OK "5. StandardAttributes: $($stdAttrs.Count) entries"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Report-OK "5. StandardAttributes: N/A for $mdType"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 6: ChildObjects — allowed element types ---
|
||||
|
||||
$childObjNode = $typeNode.SelectSingleNode("md:ChildObjects", $ns)
|
||||
$allowedChildren = $childObjectRules[$mdType]
|
||||
|
||||
if ($childObjNode) {
|
||||
$check6Ok = $true
|
||||
$childCounts = @{}
|
||||
|
||||
foreach ($child in $childObjNode.ChildNodes) {
|
||||
if ($child.NodeType -ne 'Element') { continue }
|
||||
$childTag = $child.LocalName
|
||||
|
||||
if ($allowedChildren -notcontains $childTag) {
|
||||
Report-Error "6. ChildObjects: disallowed element '$childTag' for $mdType"
|
||||
$check6Ok = $false
|
||||
}
|
||||
|
||||
if (-not $childCounts.ContainsKey($childTag)) {
|
||||
$childCounts[$childTag] = 0
|
||||
}
|
||||
$childCounts[$childTag]++
|
||||
}
|
||||
|
||||
if ($check6Ok) {
|
||||
$summary = ($childCounts.GetEnumerator() | Sort-Object Name | ForEach-Object { "$($_.Name)($($_.Value))" }) -join ", "
|
||||
if ($summary) {
|
||||
Report-OK "6. ChildObjects types: $summary"
|
||||
} else {
|
||||
Report-OK "6. ChildObjects: empty (valid for $mdType)"
|
||||
}
|
||||
}
|
||||
} elseif ($allowedChildren.Count -eq 0) {
|
||||
Report-OK "6. ChildObjects: absent (correct for $mdType)"
|
||||
} else {
|
||||
# Some types may have no children — that's OK
|
||||
Report-OK "6. ChildObjects: absent"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 7: Attributes/Dimensions/Resources/EnumValues/Columns — UUID, Name, Type ---
|
||||
|
||||
function Check-ChildElement {
|
||||
param(
|
||||
[System.Xml.XmlNode]$node,
|
||||
[string]$kind,
|
||||
[bool]$requireType
|
||||
)
|
||||
|
||||
$uuid = $node.GetAttribute("uuid")
|
||||
if (-not $uuid) {
|
||||
Report-Error "7. $kind missing uuid"
|
||||
return $false
|
||||
} elseif ($uuid -notmatch $guidPattern) {
|
||||
Report-Error "7. $kind has invalid uuid '$uuid'"
|
||||
return $false
|
||||
}
|
||||
|
||||
$elProps = $node.SelectSingleNode("md:Properties", $ns)
|
||||
if (-not $elProps) {
|
||||
Report-Error "7. $kind (uuid=$uuid) missing Properties"
|
||||
return $false
|
||||
}
|
||||
|
||||
$elName = $elProps.SelectSingleNode("md:Name", $ns)
|
||||
if (-not $elName -or -not $elName.InnerText) {
|
||||
Report-Error "7. $kind (uuid=$uuid) missing or empty Name"
|
||||
return $false
|
||||
}
|
||||
|
||||
$nameVal = $elName.InnerText
|
||||
if ($nameVal -notmatch $identPattern) {
|
||||
Report-Error "7. $kind '$nameVal' has invalid identifier"
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($requireType) {
|
||||
$typeEl = $elProps.SelectSingleNode("md:Type", $ns)
|
||||
if (-not $typeEl) {
|
||||
Report-Error "7. $kind '$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 "7. $kind '$nameVal' Type block has no v8:Type or v8:TypeSet"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($childObjNode) {
|
||||
$check7Ok = $true
|
||||
$check7Count = 0
|
||||
$elementKinds = @("Attribute","Dimension","Resource","EnumValue","Column")
|
||||
|
||||
foreach ($kind in $elementKinds) {
|
||||
$elements = $childObjNode.SelectNodes("md:$kind", $ns)
|
||||
$requireType = ($kind -ne "EnumValue")
|
||||
foreach ($el in $elements) {
|
||||
if ($script:stopped) { break }
|
||||
$ok = Check-ChildElement -node $el -kind $kind -requireType $requireType
|
||||
if (-not $ok) { $check7Ok = $false }
|
||||
$check7Count++
|
||||
}
|
||||
}
|
||||
|
||||
if ($check7Ok -and $check7Count -gt 0) {
|
||||
Report-OK "7. Child elements: $check7Count items checked (UUID, Name, Type)"
|
||||
} elseif ($check7Count -eq 0) {
|
||||
Report-OK "7. Child elements: none to check"
|
||||
}
|
||||
} else {
|
||||
Report-OK "7. Child elements: N/A (no ChildObjects)"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 8: Name uniqueness ---
|
||||
|
||||
function Check-Uniqueness {
|
||||
param(
|
||||
[System.Xml.XmlNodeList]$nodes,
|
||||
[string]$kind
|
||||
)
|
||||
|
||||
$names = @{}
|
||||
$hasDupes = $false
|
||||
|
||||
foreach ($node in $nodes) {
|
||||
$elProps = $node.SelectSingleNode("md:Properties", $ns)
|
||||
if (-not $elProps) { continue }
|
||||
$elName = $elProps.SelectSingleNode("md:Name", $ns)
|
||||
if (-not $elName -or -not $elName.InnerText) { continue }
|
||||
|
||||
$nameVal = $elName.InnerText
|
||||
if ($names.ContainsKey($nameVal)) {
|
||||
Report-Error "8. Duplicate $kind name: '$nameVal'"
|
||||
$hasDupes = $true
|
||||
} else {
|
||||
$names[$nameVal] = $true
|
||||
}
|
||||
}
|
||||
|
||||
return (-not $hasDupes)
|
||||
}
|
||||
|
||||
if ($childObjNode) {
|
||||
$check8Ok = $true
|
||||
|
||||
# Attributes
|
||||
$attrs = $childObjNode.SelectNodes("md:Attribute", $ns)
|
||||
if ($attrs.Count -gt 0) {
|
||||
if (-not (Check-Uniqueness -nodes $attrs -kind "Attribute")) { $check8Ok = $false }
|
||||
}
|
||||
|
||||
# TabularSections
|
||||
$tss = $childObjNode.SelectNodes("md:TabularSection", $ns)
|
||||
if ($tss.Count -gt 0) {
|
||||
if (-not (Check-Uniqueness -nodes $tss -kind "TabularSection")) { $check8Ok = $false }
|
||||
}
|
||||
|
||||
# Dimensions
|
||||
$dims = $childObjNode.SelectNodes("md:Dimension", $ns)
|
||||
if ($dims.Count -gt 0) {
|
||||
if (-not (Check-Uniqueness -nodes $dims -kind "Dimension")) { $check8Ok = $false }
|
||||
}
|
||||
|
||||
# Resources
|
||||
$ress = $childObjNode.SelectNodes("md:Resource", $ns)
|
||||
if ($ress.Count -gt 0) {
|
||||
if (-not (Check-Uniqueness -nodes $ress -kind "Resource")) { $check8Ok = $false }
|
||||
}
|
||||
|
||||
# EnumValues
|
||||
$evs = $childObjNode.SelectNodes("md:EnumValue", $ns)
|
||||
if ($evs.Count -gt 0) {
|
||||
if (-not (Check-Uniqueness -nodes $evs -kind "EnumValue")) { $check8Ok = $false }
|
||||
}
|
||||
|
||||
# Columns (DocumentJournal)
|
||||
$cols = $childObjNode.SelectNodes("md:Column", $ns)
|
||||
if ($cols.Count -gt 0) {
|
||||
if (-not (Check-Uniqueness -nodes $cols -kind "Column")) { $check8Ok = $false }
|
||||
}
|
||||
|
||||
# URLTemplates (HTTPService)
|
||||
$urlTs = $childObjNode.SelectNodes("md:URLTemplate", $ns)
|
||||
if ($urlTs.Count -gt 0) {
|
||||
if (-not (Check-Uniqueness -nodes $urlTs -kind "URLTemplate")) { $check8Ok = $false }
|
||||
}
|
||||
|
||||
# Operations (WebService)
|
||||
$ops = $childObjNode.SelectNodes("md:Operation", $ns)
|
||||
if ($ops.Count -gt 0) {
|
||||
if (-not (Check-Uniqueness -nodes $ops -kind "Operation")) { $check8Ok = $false }
|
||||
}
|
||||
|
||||
if ($check8Ok) {
|
||||
Report-OK "8. Name uniqueness: all names unique"
|
||||
}
|
||||
} else {
|
||||
Report-OK "8. Name uniqueness: N/A"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 9: TabularSections — internal structure ---
|
||||
|
||||
if ($childObjNode) {
|
||||
$tsSections = $childObjNode.SelectNodes("md:TabularSection", $ns)
|
||||
if ($tsSections.Count -gt 0) {
|
||||
$check9Ok = $true
|
||||
$tsCount = 0
|
||||
|
||||
foreach ($ts in $tsSections) {
|
||||
if ($script:stopped) { break }
|
||||
$tsCount++
|
||||
|
||||
# UUID
|
||||
$tsUuid = $ts.GetAttribute("uuid")
|
||||
if (-not $tsUuid -or $tsUuid -notmatch $guidPattern) {
|
||||
Report-Error "9. TabularSection #${tsCount}: invalid or missing uuid"
|
||||
$check9Ok = $false
|
||||
}
|
||||
|
||||
# Name
|
||||
$tsProps = $ts.SelectSingleNode("md:Properties", $ns)
|
||||
$tsNameNode = if ($tsProps) { $tsProps.SelectSingleNode("md:Name", $ns) } else { $null }
|
||||
$tsName = if ($tsNameNode) { $tsNameNode.InnerText } else { "(unnamed)" }
|
||||
|
||||
if (-not $tsNameNode -or -not $tsNameNode.InnerText) {
|
||||
Report-Error "9. TabularSection #${tsCount}: missing or empty Name"
|
||||
$check9Ok = $false
|
||||
}
|
||||
|
||||
# InternalInfo with 2 GeneratedType (TabularSection + TabularSectionRow)
|
||||
$tsIntInfo = $ts.SelectSingleNode("md:InternalInfo", $ns)
|
||||
if ($tsIntInfo) {
|
||||
$tsGens = $tsIntInfo.SelectNodes("xr:GeneratedType", $ns)
|
||||
if ($tsGens.Count -lt 2) {
|
||||
Report-Warn "9. TabularSection '$tsName': expected 2 GeneratedType, found $($tsGens.Count)"
|
||||
}
|
||||
}
|
||||
|
||||
# Attributes inside TS
|
||||
$tsChildObj = $ts.SelectSingleNode("md:ChildObjects", $ns)
|
||||
if ($tsChildObj) {
|
||||
$tsAttrs = $tsChildObj.SelectNodes("md:Attribute", $ns)
|
||||
$tsAttrNames = @{}
|
||||
foreach ($ta in $tsAttrs) {
|
||||
$taOk = Check-ChildElement -node $ta -kind "TabularSection '$tsName'.Attribute" -requireType $true
|
||||
if (-not $taOk) { $check9Ok = $false }
|
||||
|
||||
# Check name uniqueness within TS
|
||||
$taProps = $ta.SelectSingleNode("md:Properties", $ns)
|
||||
$taName = if ($taProps) { $taProps.SelectSingleNode("md:Name", $ns) } else { $null }
|
||||
if ($taName -and $taName.InnerText) {
|
||||
if ($tsAttrNames.ContainsKey($taName.InnerText)) {
|
||||
Report-Error "9. Duplicate attribute '$($taName.InnerText)' in TabularSection '$tsName'"
|
||||
$check9Ok = $false
|
||||
} else {
|
||||
$tsAttrNames[$taName.InnerText] = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# StandardAttributes of TS: expect LineNumber
|
||||
$tsStdAttr = $tsProps.SelectSingleNode("md:StandardAttributes", $ns)
|
||||
if ($tsStdAttr) {
|
||||
$tsStdAttrs = $tsStdAttr.SelectNodes("xr:StandardAttribute", $ns)
|
||||
$hasLineNumber = $false
|
||||
foreach ($tsa in $tsStdAttrs) {
|
||||
if ($tsa.GetAttribute("name") -eq "LineNumber") { $hasLineNumber = $true }
|
||||
}
|
||||
if (-not $hasLineNumber) {
|
||||
Report-Warn "9. TabularSection '$tsName': missing LineNumber StandardAttribute"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($check9Ok) {
|
||||
Report-OK "9. TabularSections: $tsCount sections, structure valid"
|
||||
}
|
||||
} else {
|
||||
Report-OK "9. TabularSections: none present"
|
||||
}
|
||||
} else {
|
||||
Report-OK "9. TabularSections: N/A"
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 10: Cross-property consistency ---
|
||||
|
||||
$check10Ok = $true
|
||||
$check10Issues = 0
|
||||
|
||||
if ($propsNode) {
|
||||
# HierarchyType set but Hierarchical = false
|
||||
$hierarchical = $propsNode.SelectSingleNode("md:Hierarchical", $ns)
|
||||
$hierarchyType = $propsNode.SelectSingleNode("md:HierarchyType", $ns)
|
||||
if ($hierarchical -and $hierarchyType -and $hierarchical.InnerText -eq "false" -and $hierarchyType.InnerText) {
|
||||
Report-Warn "10. HierarchyType='$($hierarchyType.InnerText)' but Hierarchical=false"
|
||||
$check10Issues++
|
||||
}
|
||||
|
||||
# CommonModule: no context enabled
|
||||
if ($mdType -eq "CommonModule") {
|
||||
$contexts = @("Server","ClientManagedApplication","ClientOrdinaryApplication","ExternalConnection","ServerCall","Global")
|
||||
$anyEnabled = $false
|
||||
foreach ($ctx in $contexts) {
|
||||
$ctxNode = $propsNode.SelectSingleNode("md:$ctx", $ns)
|
||||
if ($ctxNode -and $ctxNode.InnerText -eq "true") {
|
||||
$anyEnabled = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $anyEnabled) {
|
||||
Report-Warn "10. CommonModule: no execution context enabled"
|
||||
$check10Issues++
|
||||
}
|
||||
}
|
||||
|
||||
# EventSubscription: empty Handler
|
||||
if ($mdType -eq "EventSubscription") {
|
||||
$handler = $propsNode.SelectSingleNode("md:Handler", $ns)
|
||||
if (-not $handler -or -not $handler.InnerText.Trim()) {
|
||||
Report-Error "10. EventSubscription: empty Handler"
|
||||
$check10Ok = $false
|
||||
$check10Issues++
|
||||
}
|
||||
|
||||
# Empty Source
|
||||
$source = $propsNode.SelectSingleNode("md:Source", $ns)
|
||||
$hasSource = $false
|
||||
if ($source) {
|
||||
$sourceTypes = $source.SelectNodes("v8:Type", $ns)
|
||||
if ($sourceTypes.Count -gt 0) { $hasSource = $true }
|
||||
}
|
||||
if (-not $hasSource) {
|
||||
Report-Warn "10. EventSubscription: no Source types specified"
|
||||
$check10Issues++
|
||||
}
|
||||
}
|
||||
|
||||
# ScheduledJob: empty MethodName
|
||||
if ($mdType -eq "ScheduledJob") {
|
||||
$method = $propsNode.SelectSingleNode("md:MethodName", $ns)
|
||||
if (-not $method -or -not $method.InnerText.Trim()) {
|
||||
Report-Error "10. ScheduledJob: empty MethodName"
|
||||
$check10Ok = $false
|
||||
$check10Issues++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($check10Ok -and $check10Issues -eq 0) {
|
||||
Report-OK "10. Cross-property consistency"
|
||||
} elseif ($check10Ok) {
|
||||
# Had warnings but no errors — already reported
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 11: HTTPService/WebService nested structure ---
|
||||
|
||||
if ($mdType -eq "HTTPService" -and $childObjNode) {
|
||||
$urlTemplates = $childObjNode.SelectNodes("md:URLTemplate", $ns)
|
||||
$check11Ok = $true
|
||||
$methodCount = 0
|
||||
|
||||
$validHTTPMethods = @("GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS","MERGE","CONNECT")
|
||||
|
||||
foreach ($ut in $urlTemplates) {
|
||||
if ($script:stopped) { break }
|
||||
|
||||
$utProps = $ut.SelectSingleNode("md:Properties", $ns)
|
||||
$utNameNode = if ($utProps) { $utProps.SelectSingleNode("md:Name", $ns) } else { $null }
|
||||
$utName = if ($utNameNode) { $utNameNode.InnerText } else { "(unnamed)" }
|
||||
|
||||
# Template property
|
||||
$tpl = if ($utProps) { $utProps.SelectSingleNode("md:Template", $ns) } else { $null }
|
||||
if (-not $tpl -or -not $tpl.InnerText.Trim()) {
|
||||
Report-Error "11. HTTPService URLTemplate '$utName': empty Template"
|
||||
$check11Ok = $false
|
||||
}
|
||||
|
||||
# Methods inside URLTemplate
|
||||
$utChildObj = $ut.SelectSingleNode("md:ChildObjects", $ns)
|
||||
if ($utChildObj) {
|
||||
$methods = $utChildObj.SelectNodes("md:Method", $ns)
|
||||
foreach ($m in $methods) {
|
||||
$methodCount++
|
||||
$mProps = $m.SelectSingleNode("md:Properties", $ns)
|
||||
if ($mProps) {
|
||||
$httpMethod = $mProps.SelectSingleNode("md:HTTPMethod", $ns)
|
||||
if ($httpMethod -and $httpMethod.InnerText) {
|
||||
if ($validHTTPMethods -notcontains $httpMethod.InnerText) {
|
||||
Report-Error "11. HTTPService URLTemplate '$utName': invalid HTTPMethod '$($httpMethod.InnerText)'"
|
||||
$check11Ok = $false
|
||||
}
|
||||
} else {
|
||||
Report-Error "11. HTTPService URLTemplate '$utName': Method missing HTTPMethod"
|
||||
$check11Ok = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($check11Ok) {
|
||||
Report-OK "11. HTTPService: $($urlTemplates.Count) URLTemplate(s), $methodCount method(s)"
|
||||
}
|
||||
} elseif ($mdType -eq "WebService" -and $childObjNode) {
|
||||
$operations = $childObjNode.SelectNodes("md:Operation", $ns)
|
||||
$check11Ok = $true
|
||||
$paramCount = 0
|
||||
|
||||
$validDirections = @("In","Out","InOut")
|
||||
|
||||
foreach ($op in $operations) {
|
||||
if ($script:stopped) { break }
|
||||
|
||||
$opProps = $op.SelectSingleNode("md:Properties", $ns)
|
||||
$opNameNode = if ($opProps) { $opProps.SelectSingleNode("md:Name", $ns) } else { $null }
|
||||
$opName = if ($opNameNode) { $opNameNode.InnerText } else { "(unnamed)" }
|
||||
|
||||
# ReturnType — XDTOReturningValueType
|
||||
$retType = if ($opProps) { $opProps.SelectSingleNode("md:XDTOReturningValueType", $ns) } else { $null }
|
||||
if (-not $retType -or -not $retType.InnerText.Trim()) {
|
||||
Report-Warn "11. WebService Operation '$opName': no XDTOReturningValueType"
|
||||
}
|
||||
|
||||
# Parameters inside Operation
|
||||
$opChildObj = $op.SelectSingleNode("md:ChildObjects", $ns)
|
||||
if ($opChildObj) {
|
||||
$params = $opChildObj.SelectNodes("md:Parameter", $ns)
|
||||
foreach ($p in $params) {
|
||||
$paramCount++
|
||||
$pProps = $p.SelectSingleNode("md:Properties", $ns)
|
||||
if ($pProps) {
|
||||
$dir = $pProps.SelectSingleNode("md:TransferDirection", $ns)
|
||||
if ($dir -and $dir.InnerText -and $validDirections -notcontains $dir.InnerText) {
|
||||
Report-Error "11. WebService Operation '$opName': Parameter has invalid TransferDirection '$($dir.InnerText)'"
|
||||
$check11Ok = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($check11Ok) {
|
||||
Report-OK "11. WebService: $($operations.Count) operation(s), $paramCount parameter(s)"
|
||||
}
|
||||
} else {
|
||||
Report-OK "11. HTTPService/WebService: N/A"
|
||||
}
|
||||
|
||||
# --- Final output ---
|
||||
|
||||
& $finalize
|
||||
|
||||
if ($script:errors -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
Reference in New Issue
Block a user