feat(meta-decompile): разбор внешних источников данных и раундтрип

Декомпилятор собирает DSL источника целиком: читает файл источника, подтягивает файлы таблиц
из <Источник>/Tables/ и складывает поля, ключи, ссылки и функции обратно в тот же синтаксис,
который принимает meta-compile. Ссылки на поля возвращаются короткими именами, таблица без
собственных свойств — коротким массивом полей, функция с типом по умолчанию — одной строкой.

Это замыкает дешёвый контур проверки: XML → декомпиляция → компиляция → сравнение с исходником,
без 1С и без Docker. На нём и проверено: наша выгрузка, выгрузка платформы и четыре таблицы
внешних источников из чужого рабочего проекта возвращаются байт в байт. У выгрузки платформы
остаются два известных расхождения: значение незаполненного родителя (платформа сама сбрасывает
его при любой загрузке) и формы (декомпилятор их не захватывает — так задумано).

Побочно закрыт дефект, к внешним источникам не относящийся: xs:base64Binary разбирался как
ХранилищеЗначения, хотя это ДвоичныеДанные. Различать их можно по квалификаторам —
у ХранилищеЗначения платформа пишет v8:ValueStorage, а у двоичных данных есть
BinaryDataQualifiers. Компилятор научен типу BinaryData (и BinaryData(N)) — до этого он
принимал такое имя, но эмитил его как есть, то есть невалидный XML.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBsZA5cr2WFThtgp7i5WVi
This commit is contained in:
Nick Shirokov
2026-09-06 14:32:43 +03:00
co-authored by Claude Opus 5
parent 123d6e326b
commit 24cfe6a0a6
10 changed files with 920 additions and 9 deletions
@@ -97,8 +97,9 @@
]
```
Допустимые типы: `Number`, `String`, `Date`, `Boolean`, `UUID`, `BinaryData` и ссылка на таблицу
внешнего источника — `ExternalDataSourceTableRef.<Источник>.<Таблица>`.
Допустимые типы: `Number`, `String`, `Date`, `Boolean`, `UUID`, `BinaryData` (двоичные данные;
`BinaryData(N)` — с ограничением длины) и ссылка на таблицу внешнего источника —
`ExternalDataSourceTableRef.<Источник>.<Таблица>`.
**Составной тип у поля недопустим** — платформа такую конфигурацию не загружает.
@@ -1,4 +1,4 @@
# meta-compile v1.106 — Compile 1C metadata object from JSON
# meta-compile v1.107 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -604,6 +604,10 @@ $script:typeSynonyms["bool"] = "Boolean"
# ValueStorage / UUID — прощающий ввод (модель может написать base64Binary / рус. форму → канон).
$script:typeSynonyms["valuestorage"] = "ValueStorage"
$script:typeSynonyms["base64binary"] = "ValueStorage"
# ДвоичныеДанные — ОТДЕЛЬНЫЙ тип, не ХранилищеЗначения: платформа пишет его как
# xs:base64Binary с квалификаторами. Встречается у полей внешних источников данных.
$script:typeSynonyms["binarydata"] = "BinaryData"
$script:typeSynonyms["двоичныеданные"] = "BinaryData"
$script:typeSynonyms["хранилищезначений"] = "ValueStorage"
$script:typeSynonyms["хранилищезначения"] = "ValueStorage"
$script:typeSynonyms["uuid"] = "UUID"
@@ -807,6 +811,17 @@ function Emit-TypeContent {
}
# ValueStorage (ХранилищеЗначения) — канон v8:ValueStorage (не xs:base64Binary, хоть 1С и принимает оба).
# ДвоичныеДанные — xs:base64Binary с квалификаторами (у полей внешних источников).
if ($typeStr -eq "BinaryData" -or $typeStr -match '^BinaryData\(') {
$blen = if ($typeStr -match '^BinaryData\((\d+)\)$') { $Matches[1] } else { "4294967292" }
$ballowed = if ($typeStr -match '^BinaryData\(\d+\)$') { "Variable" } else { "Fixed" }
X "$indent<v8:Type>xs:base64Binary</v8:Type>"
X "$indent<v8:BinaryDataQualifiers>"
X "$indent`t<v8:Length>$blen</v8:Length>"
X "$indent`t<v8:AllowedLength>$ballowed</v8:AllowedLength>"
X "$indent</v8:BinaryDataQualifiers>"
return
}
if ($typeStr -eq "ValueStorage") {
X "$indent<v8:Type>v8:ValueStorage</v8:Type>"
return
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.106 — Compile 1C metadata object from JSON
# meta-compile v1.107 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -735,6 +735,10 @@ type_synonyms = {
# ValueStorage / UUID — прощающий ввод (base64Binary / рус. форма → канон).
'valuestorage': 'ValueStorage',
'base64binary': 'ValueStorage',
# ДвоичныеДанные — ОТДЕЛЬНЫЙ тип, не ХранилищеЗначения: платформа пишет его как
# xs:base64Binary с квалификаторами. Встречается у полей внешних источников данных.
'binarydata': 'BinaryData',
'двоичныеданные': 'BinaryData',
'хранилищезначений': 'ValueStorage',
'хранилищезначения': 'ValueStorage',
'uuid': 'UUID',
@@ -927,6 +931,17 @@ def emit_type_content(indent, type_str):
if re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef|AnyRef|AnyIBRef)$', type_str):
X(f'{indent}<v8:TypeSet>cfg:{type_str}</v8:TypeSet>')
return
# ДвоичныеДанные — xs:base64Binary с квалификаторами (у полей внешних источников).
m_bin = re.match(r'^BinaryData(?:\((\d+)\))?$', type_str)
if m_bin:
blen = m_bin.group(1) or '4294967292'
ballowed = 'Variable' if m_bin.group(1) else 'Fixed'
X(f'{indent}<v8:Type>xs:base64Binary</v8:Type>')
X(f'{indent}<v8:BinaryDataQualifiers>')
X(f'{indent}\t<v8:Length>{blen}</v8:Length>')
X(f'{indent}\t<v8:AllowedLength>{ballowed}</v8:AllowedLength>')
X(f'{indent}</v8:BinaryDataQualifiers>')
return
# ValueStorage (ХранилищеЗначения) — канон v8:ValueStorage (не xs:base64Binary).
if type_str == 'ValueStorage':
X(f'{indent}<v8:Type>v8:ValueStorage</v8:Type>')