fix(form-compile,form-validate): warn on invalid XDTO types, add Check 12

form-compile now warns when model uses runtime types like
FormDataStructure that don't exist in XML schema. Expanded cfg:
regex to cover all 25 known prefixes.

form-validate adds Check 12 — validates all <v8:Type> values:
ERROR for known-invalid types, WARN for unrecognized bare types,
pass-through for unknown namespaced types (future-proof).

Updated SKILL.md with full type reference and invalid type warning.
Updated docs/1c-form-spec.md with missing type groups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-04-05 18:07:07 +03:00
co-authored by Claude Opus 4.6
parent ff068202e3
commit dd88f78969
8 changed files with 264 additions and 17 deletions
+34 -5
View File
@@ -241,6 +241,8 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
### Система типов
**Примитивные:**
| DSL | XML |
|------------------------|----------------------------------------|
| `"string"` / `"string(100)"` | `xs:string` + StringQualifiers |
@@ -248,11 +250,38 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
| `"decimal(10,0,nonneg)"` | с AllowedSign=Nonnegative |
| `"boolean"` | `xs:boolean` |
| `"date"` / `"dateTime"` / `"time"` | `xs:dateTime` + DateFractions |
| `"CatalogRef.XXX"` | `cfg:CatalogRef.XXX` |
| `"DocumentRef.XXX"` | `cfg:DocumentRef.XXX` |
| `"ValueTable"` | `v8:ValueTable` |
| `"ValueList"` | `v8:ValueListType` |
| `"Type1 \| Type2"` | составной тип |
**Ссылочные и объектные (`cfg:Prefix.Name`):**
| DSL | Описание |
|-----|----------|
| `"CatalogRef.XXX"` / `"CatalogObject.XXX"` | Справочник |
| `"DocumentRef.XXX"` / `"DocumentObject.XXX"` | Документ |
| `"EnumRef.XXX"` | Перечисление |
| `"DataProcessorObject.XXX"` / `"ReportObject.XXX"` | Обработка / Отчёт |
| `"InformationRegisterRecordSet.XXX"` | Набор записей регистра сведений |
| `"AccumulationRegisterRecordSet.XXX"` | Набор записей регистра накопления |
| `"DynamicList"` | Динамический список |
Также допустимы: `ChartOfAccountsRef/Object`, `ChartOfCharacteristicTypesRef/Object`, `ChartOfCalculationTypesRef/Object`, `ExchangePlanRef/Object`, `BusinessProcessRef/Object`, `TaskRef/Object`, `AccountingRegisterRecordSet`, `InformationRegisterRecordManager`, `ConstantsSet`.
**Платформенные:**
| DSL | XML |
|-----|-----|
| `"ValueTable"` | `v8:ValueTable` |
| `"ValueTree"` | `v8:ValueTree` |
| `"ValueList"` | `v8:ValueListType` |
| `"TypeDescription"` | `v8:TypeDescription` |
| `"UUID"` | `v8:UUID` |
| `"FormattedString"` | `v8ui:FormattedString` |
| `"Picture"` / `"Color"` / `"Font"` | `v8ui:*` |
| `"DataCompositionSettings"` | `dcsset:DataCompositionSettings` |
| `"Type1 \| Type2"` | составной тип (несколько `<v8:Type>`) |
**Недопустимые типы (XDTO-ошибка при загрузке):**
> `FormDataStructure`, `FormDataCollection`, `FormDataTree` — runtime-типы 1С, не существуют в XML-схеме. Вместо них используйте `CatalogObject.XXX`, `DocumentObject.XXX`, `DataProcessorObject.XXX`, `ValueTable`, `ValueTree`.
## Связки: элемент + реквизит
@@ -1,4 +1,4 @@
# form-compile v1.0 — Compile 1C managed form from JSON
# form-compile v1.1 — Compile 1C managed form from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -80,6 +80,20 @@ $script:formTypeSynonyms["бизнеспроцессссылка"] = "
$script:formTypeSynonyms["задачассылка"] = "TaskRef"
$script:formTypeSynonyms["определяемыйтип"] = "DefinedType"
# Known invalid types (runtime/UI types that don't exist in XDTO schema)
$script:knownInvalidTypes = @{
"FormDataStructure" = "Runtime type. Use cfg:*Object.XXX (e.g. CatalogObject.XXX)"
"FormDataCollection" = "Runtime type. Use ValueTable"
"FormDataTree" = "Runtime type. Use ValueTree"
"FormDataTreeItem" = "Runtime type, not valid in XML"
"FormDataCollectionItem"= "Runtime type, not valid in XML"
"FormGroup" = "UI element type, not a data type"
"FormField" = "UI element type, not a data type"
"FormButton" = "UI element type, not a data type"
"FormDecoration" = "UI element type, not a data type"
"FormTable" = "UI element type, not a data type"
}
function Resolve-TypeStr {
param([string]$typeStr)
if (-not $typeStr) { return $typeStr }
@@ -219,15 +233,21 @@ function Emit-SingleType {
}
# cfg: references (CatalogRef.XXX, DocumentObject.XXX, etc.)
if ($typeStr -match '^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef|InformationRegisterRecordSet|AccumulationRegisterRecordSet|DataProcessorObject)\.') {
if ($typeStr -match '^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|ChartOfAccountsRef|ChartOfAccountsObject|ChartOfCharacteristicTypesRef|ChartOfCharacteristicTypesObject|ChartOfCalculationTypesRef|ChartOfCalculationTypesObject|ExchangePlanRef|ExchangePlanObject|BusinessProcessRef|BusinessProcessObject|TaskRef|TaskObject|InformationRegisterRecordSet|InformationRegisterRecordManager|AccumulationRegisterRecordSet|AccountingRegisterRecordSet|ConstantsSet|DataProcessorObject|ReportObject)\.') {
X "$indent<v8:Type>cfg:$typeStr</v8:Type>"
return
}
# Fallback: emit as-is with cfg: prefix if contains dot, otherwise v8:
# Fallback with validation
if ($script:knownInvalidTypes.ContainsKey($typeStr)) {
Write-Warning "Type '$typeStr': $($script:knownInvalidTypes[$typeStr])"
}
if ($typeStr.Contains('.')) {
X "$indent<v8:Type>cfg:$typeStr</v8:Type>"
} else {
if (-not $script:knownInvalidTypes.ContainsKey($typeStr)) {
Write-Warning "Unrecognized bare type '$typeStr' — will be emitted without namespace prefix"
}
X "$indent<v8:Type>$typeStr</v8:Type>"
}
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-compile v1.0 — Compile 1C managed form from JSON
# form-compile v1.1 — Compile 1C managed form from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -202,11 +202,27 @@ DCS_MAP = {
CFG_REF_PATTERN = re.compile(
r'^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|'
r'ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|'
r'ExchangePlanRef|BusinessProcessRef|TaskRef|'
r'InformationRegisterRecordSet|AccumulationRegisterRecordSet|DataProcessorObject)\.'
r'ChartOfAccountsRef|ChartOfAccountsObject|ChartOfCharacteristicTypesRef|ChartOfCharacteristicTypesObject|'
r'ChartOfCalculationTypesRef|ChartOfCalculationTypesObject|'
r'ExchangePlanRef|ExchangePlanObject|BusinessProcessRef|BusinessProcessObject|TaskRef|TaskObject|'
r'InformationRegisterRecordSet|InformationRegisterRecordManager|'
r'AccumulationRegisterRecordSet|AccountingRegisterRecordSet|'
r'ConstantsSet|DataProcessorObject|ReportObject)\.'
)
KNOWN_INVALID_TYPES = {
'FormDataStructure': 'Runtime type. Use cfg:*Object.XXX (e.g. CatalogObject.XXX)',
'FormDataCollection': 'Runtime type. Use ValueTable',
'FormDataTree': 'Runtime type. Use ValueTree',
'FormDataTreeItem': 'Runtime type, not valid in XML',
'FormDataCollectionItem': 'Runtime type, not valid in XML',
'FormGroup': 'UI element type, not a data type',
'FormField': 'UI element type, not a data type',
'FormButton': 'UI element type, not a data type',
'FormDecoration': 'UI element type, not a data type',
'FormTable': 'UI element type, not a data type',
}
_FORM_TYPE_SYNONYMS = {
"строка": "string", "число": "decimal", "булево": "boolean",
@@ -312,10 +328,14 @@ def emit_single_type(lines, type_str, indent):
lines.append(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
return
# Fallback
# Fallback with validation
if type_str in KNOWN_INVALID_TYPES:
print(f"WARNING: Type '{type_str}': {KNOWN_INVALID_TYPES[type_str]}", file=sys.stderr)
if '.' in type_str:
lines.append(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
else:
if type_str not in KNOWN_INVALID_TYPES:
print(f"WARNING: Unrecognized bare type '{type_str}' — will be emitted without namespace prefix", file=sys.stderr)
lines.append(f'{indent}<v8:Type>{type_str}</v8:Type>')
@@ -1,4 +1,4 @@
# form-validate v1.1 — Validate 1C managed form
# form-validate v1.2 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -667,6 +667,75 @@ if (-not $stopped -and -not $isExtension) {
}
}
# --- Check 12: Type values validation ---
$knownInvalidTypes = @(
"FormDataStructure","FormDataCollection","FormDataTree","FormDataTreeItem","FormDataCollectionItem"
"FormGroup","FormField","FormButton","FormDecoration","FormTable"
)
$validClosedTypes = @(
"xs:boolean","xs:string","xs:decimal","xs:dateTime","xs:binary"
"v8:FillChecking","v8:Null","v8:StandardPeriod","v8:StandardBeginningDate","v8:Type"
"v8:TypeDescription","v8:UUID","v8:ValueListType","v8:ValueTable","v8:ValueTree"
"v8:Universal","v8:FixedArray","v8:FixedStructure"
"v8ui:Color","v8ui:Font","v8ui:FormattedString","v8ui:HorizontalAlign"
"v8ui:Picture","v8ui:SizeChangeMode","v8ui:VerticalAlign"
"dcsset:DataCompositionComparisonType","dcsset:DataCompositionFieldPlacement"
"dcsset:Filter","dcsset:SettingsComposer","dcsset:DataCompositionSettings"
"dcssch:DataCompositionSchema"
"dcscor:DataCompositionComparisonType","dcscor:DataCompositionGroupType"
"dcscor:DataCompositionPeriodAdditionType","dcscor:DataCompositionSortDirection","dcscor:Field"
"ent:AccountType","ent:AccumulationRecordType","ent:AccountingRecordType"
)
$validCfgPrefixes = @(
"AccountingRegisterRecordSet","AccumulationRegisterRecordSet"
"BusinessProcessObject","BusinessProcessRef"
"CatalogObject","CatalogRef"
"ChartOfAccountsObject","ChartOfAccountsRef"
"ChartOfCalculationTypesObject","ChartOfCalculationTypesRef"
"ChartOfCharacteristicTypesObject","ChartOfCharacteristicTypesRef"
"ConstantsSet","DataProcessorObject","DocumentObject","DocumentRef"
"DynamicList","EnumRef","ExchangePlanObject","ExchangePlanRef"
"InformationRegisterRecordManager","InformationRegisterRecordSet"
"ReportObject","TaskObject","TaskRef"
)
if (-not $stopped) {
$typeNodes = $root.SelectNodes("//v8:Type", $nsMgr)
$typeOk = $true
$typeChecked = 0
$typeInvalid = 0
foreach ($tn in $typeNodes) {
$tv = $tn.InnerText.Trim()
if (-not $tv) { continue }
$typeChecked++
if ($tv -in $knownInvalidTypes) {
Report-Error "12. Type '$tv': invalid runtime/UI type (not valid in XDTO schema)"
$typeOk = $false; $typeInvalid++
continue
}
if ($tv -in $validClosedTypes) { continue }
if ($tv -match '^cfg:(.+)$') {
$cfgVal = $Matches[1]
if ($cfgVal -eq "DynamicList") { continue }
if ($cfgVal -match '^([^.]+)\.') {
if ($Matches[1] -in $validCfgPrefixes) { continue }
}
Report-Warn "12. Type '$tv': unrecognized cfg prefix"
$typeOk = $false
continue
}
if ($tv -match ':') { continue }
Report-Warn "12. Type '$tv': bare type without namespace prefix"
$typeOk = $false
}
if ($typeChecked -eq 0) {
Report-OK "12. Types: no type values to check"
} elseif ($typeOk) {
Report-OK "12. Types: $typeChecked values, all valid"
}
}
# --- Summary ---
$checks = $script:okCount + $errors + $warnings
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-validate v1.1 — Validate 1C managed form
# form-validate v1.2 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -13,6 +13,40 @@ V8_NS = "http://v8.1c.ru/8.1/data/core"
NSMAP = {"f": F_NS, "v8": V8_NS}
KNOWN_INVALID_TYPES = {
'FormDataStructure', 'FormDataCollection', 'FormDataTree',
'FormDataTreeItem', 'FormDataCollectionItem',
'FormGroup', 'FormField', 'FormButton', 'FormDecoration', 'FormTable',
}
VALID_CLOSED_TYPES = {
'xs:boolean', 'xs:string', 'xs:decimal', 'xs:dateTime', 'xs:binary',
'v8:FillChecking', 'v8:Null', 'v8:StandardPeriod', 'v8:StandardBeginningDate', 'v8:Type',
'v8:TypeDescription', 'v8:UUID', 'v8:ValueListType', 'v8:ValueTable', 'v8:ValueTree',
'v8:Universal', 'v8:FixedArray', 'v8:FixedStructure',
'v8ui:Color', 'v8ui:Font', 'v8ui:FormattedString', 'v8ui:HorizontalAlign',
'v8ui:Picture', 'v8ui:SizeChangeMode', 'v8ui:VerticalAlign',
'dcsset:DataCompositionComparisonType', 'dcsset:DataCompositionFieldPlacement',
'dcsset:Filter', 'dcsset:SettingsComposer', 'dcsset:DataCompositionSettings',
'dcssch:DataCompositionSchema',
'dcscor:DataCompositionComparisonType', 'dcscor:DataCompositionGroupType',
'dcscor:DataCompositionPeriodAdditionType', 'dcscor:DataCompositionSortDirection', 'dcscor:Field',
'ent:AccountType', 'ent:AccumulationRecordType', 'ent:AccountingRecordType',
}
VALID_CFG_PREFIXES = {
'AccountingRegisterRecordSet', 'AccumulationRegisterRecordSet',
'BusinessProcessObject', 'BusinessProcessRef',
'CatalogObject', 'CatalogRef',
'ChartOfAccountsObject', 'ChartOfAccountsRef',
'ChartOfCalculationTypesObject', 'ChartOfCalculationTypesRef',
'ChartOfCharacteristicTypesObject', 'ChartOfCharacteristicTypesRef',
'ConstantsSet', 'DataProcessorObject', 'DocumentObject', 'DocumentRef',
'DynamicList', 'EnumRef', 'ExchangePlanObject', 'ExchangePlanRef',
'InformationRegisterRecordManager', 'InformationRegisterRecordSet',
'ReportObject', 'TaskObject', 'TaskRef',
}
def localname(el):
return etree.QName(el.tag).localname
@@ -588,6 +622,45 @@ def main():
if call_type_without_base:
report_warn("callType attributes found but no BaseForm \u2014 possible incorrect structure")
# --- Check 12: Type validation ---
if not stopped:
type_nodes = root.xpath('//v8:Type', namespaces={'v8': V8_NS})
type_error_count = 0
type_warn_count = 0
type_count = len(type_nodes)
for tn in type_nodes:
if stopped:
break
tv = (tn.text or "").strip()
if not tv:
continue
if tv in KNOWN_INVALID_TYPES:
report_error(f'12. Type "{tv}": invalid runtime/UI type (not valid in XDTO schema)')
type_error_count += 1
elif tv in VALID_CLOSED_TYPES:
pass # OK
elif tv.startswith("cfg:"):
suffix = tv[4:] # after "cfg:"
prefix = suffix.split(".")[0]
if prefix in VALID_CFG_PREFIXES or suffix == "DynamicList":
pass # OK
else:
report_warn(f'12. Type "{tv}": unrecognized cfg prefix')
type_warn_count += 1
elif ":" in tv:
pass # unknown namespace, pass through
else:
report_warn(f'12. Type "{tv}": bare type without namespace prefix')
type_warn_count += 1
if type_error_count == 0 and type_warn_count == 0:
if type_count > 0:
report_ok(f'12. Types: {type_count} values, all valid')
else:
report_ok('12. Types: no type values to check')
# --- Finalize ---
checks = ok_count + errors + warnings
if errors == 0 and warnings == 0 and not detailed:
+36
View File
@@ -958,7 +958,19 @@ ChildItems
| `cfg:BusinessProcessRef.<Имя>` | — | Ссылка на бизнес-процесс |
| `cfg:TaskRef.<Имя>` | — | Ссылка на задачу |
| `cfg:InformationRegisterRecordSet.<Имя>` | — | Набор записей регистра сведений |
| `cfg:InformationRegisterRecordManager.<Имя>` | — | Менеджер записи регистра сведений |
| `cfg:AccumulationRegisterRecordSet.<Имя>` | — | Набор записей регистра накопления |
| `cfg:AccountingRegisterRecordSet.<Имя>` | — | Набор записей регистра бухгалтерии |
| `cfg:ChartOfAccountsObject.<Имя>` | — | Объект плана счетов |
| `cfg:ChartOfCharacteristicTypesObject.<Имя>` | — | Объект ПВХ |
| `cfg:ChartOfCalculationTypesObject.<Имя>` | — | Объект плана видов расчёта |
| `cfg:ExchangePlanObject.<Имя>` | — | Объект плана обмена |
| `cfg:BusinessProcessObject.<Имя>` | — | Объект бизнес-процесса |
| `cfg:TaskObject.<Имя>` | — | Объект задачи |
| `cfg:ConstantsSet` | — | Набор констант |
| `cfg:DataProcessorObject.<Имя>` | — | Объект обработки |
| `cfg:ReportObject.<Имя>` | — | Объект отчёта |
| `cfg:DynamicList` | — | Динамический список |
#### Платформенные типы (v8:*)
@@ -971,6 +983,12 @@ ChildItems
| `v8:Universal` | Произвольный тип |
| `v8:FixedArray` | Фиксированный массив |
| `v8:FixedStructure` | Фиксированная структура |
| `v8:FillChecking` | Проверка заполнения |
| `v8:Null` | Null |
| `v8:StandardPeriod` | Стандартный период |
| `v8:StandardBeginningDate` | Стандартная начальная дата |
| `v8:Type` | Тип |
| `v8:UUID` | Уникальный идентификатор |
#### UI-типы (v8ui:*)
@@ -980,6 +998,9 @@ ChildItems
| `v8ui:Picture` | Картинка |
| `v8ui:Color` | Цвет |
| `v8ui:Font` | Шрифт |
| `v8ui:SizeChangeMode` | Режим изменения размера |
| `v8ui:VerticalAlign` | Вертикальное выравнивание |
| `v8ui:HorizontalAlign` | Горизонтальное выравнивание |
#### Типы СКД (dcs*:*)
@@ -988,6 +1009,21 @@ ChildItems
| `dcsset:DataCompositionSettings` | Настройки СКД |
| `dcssch:DataCompositionSchema` | Схема СКД |
| `dcscor:DataCompositionComparisonType` | Тип сравнения СКД |
| `dcsset:Filter` | Отбор СКД |
| `dcsset:SettingsComposer` | Компоновщик настроек |
| `dcsset:DataCompositionFieldPlacement` | Размещение поля СКД |
| `dcscor:DataCompositionGroupType` | Тип группировки |
| `dcscor:DataCompositionPeriodAdditionType` | Тип дополнения периода |
| `dcscor:DataCompositionSortDirection` | Направление сортировки |
| `dcscor:Field` | Поле СКД |
#### Типы предприятия (ent:*)
| Тип | Описание |
|-----|----------|
| `ent:AccountType` | Тип счёта (Активный/Пассивный/АктивноПассивный) |
| `ent:AccumulationRecordType` | Тип движения регистра накопления (Приход/Расход) |
| `ent:AccountingRecordType` | Тип бухгалтерской записи |
#### Пустой тип
@@ -15,7 +15,7 @@
<Attributes>
<Attribute name="Объект" id="1">
<Type>
<v8:Type>FormDataStructure</v8:Type>
<v8:Type>cfg:CatalogObject.Товары</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
@@ -12,7 +12,7 @@
},
{
"script": "form-compile/scripts/form-compile",
"input": { "title": "Тест", "attributes": [{ "name": "Объект", "type": "FormDataStructure", "main": true }], "elements": [{ "type": "InputField", "dataPath": "Объект.Наименование" }] },
"input": { "title": "Тест", "attributes": [{ "name": "Объект", "type": "CatalogObject.Товары", "main": true }], "elements": [{ "type": "InputField", "dataPath": "Объект.Наименование" }] },
"args": { "-JsonPath": "{inputFile}", "-OutputPath": "{workDir}/Catalogs/Товары/Forms/Форма/Ext/Form.xml" }
}
],