fix(form-compile,form-decompile): Table <Height> vs <HeightInTableRows> — развод свойств

DSL зря сводил оба тега в height, а компилятор для таблицы всегда эмитил
<HeightInTableRows> → таблица с <Height>5</Height> регенерилась как
<HeightInTableRows>5</HeightInTableRows> (форма БизнесСеть/ВыборОрганизации).
Это РАЗНЫЕ свойства (высота элемента vs высота в строках), сосуществуют в 237
таблицах корпуса (Height-only 1242, HeightInTableRows-only 755).

- Развёл: height → <Height> (generic Emit-Layout, как у всех элементов),
  новый heightInTableRows → <HeightInTableRows>.
- Декомпилятор: Add-Layout ловит <Height> → height, Table-блок
  <HeightInTableRows> → heightInTableRows.

Зеркало py. Кейс table расширен (оба тега), сертифицирован в 1С (платформа
принимает оба). Регресс 39/39 в обоих рантаймах. Раундтрип TOTAL 68→61.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-09 11:12:56 +03:00
parent 4916f5bf7c
commit 8bff8d7e05
6 changed files with 26 additions and 18 deletions
@@ -1,4 +1,4 @@
# form-compile v1.87 — Compile 1C managed form from JSON or object metadata
# form-compile v1.88 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -2603,7 +2603,7 @@ function Emit-Element {
"commandBarLocation"=1;"searchStringLocation"=1;"viewStatusLocation"=1;"searchControlLocation"=1
"excludedCommands"=1
"choiceMode"=1;"initialTreeView"=1;"enableDrag"=1;"enableStartDrag"=1
"rowPictureDataPath"=1;"tableAutofill"=1
"rowPictureDataPath"=1;"tableAutofill"=1;"heightInTableRows"=1
"rowSelectionMode"=1;"verticalLines"=1;"horizontalLines"=1
# dynamic-list table block
"defaultItem"=1;"useAlternationRowColor"=1;"fileDragMode"=1;"autoRefresh"=1
@@ -2713,7 +2713,7 @@ function Emit-CommonFlags {
# Общие layout-свойства — применимы ко всем элементам. Порядок согласован с
# историческим выводом input/label, чтобы не сдвигать существующие снапшоты.
# -skipHeight: для Table (height → HeightInTableRows, эмитится в Emit-Table).
# -skipHeight: подавить <Height> (зарезервирован; Table теперь эмитит <Height> generic-ом + свой <HeightInTableRows>).
# -multiLineDefault: input без явного autoMaxWidth при multiLine → AutoMaxWidth=false.
# Общие свойства элемента (любой тип, включая Button/cmdBar): default/skip/drag.
function Emit-CommonElementProps {
@@ -3768,7 +3768,9 @@ function Emit-Table {
if ($el.autoInsertNewRow -eq $true) { X "$inner<AutoInsertNewRow>true</AutoInsertNewRow>" }
# RowFilter — nil-плейсхолдер (всегда пустой); ключ присутствует → эмитим
if ($el.PSObject.Properties['rowFilter']) { X "$inner<RowFilter xsi:nil=`"true`"/>" }
if ($el.height) { X "$inner<HeightInTableRows>$($el.height)</HeightInTableRows>" }
# Высота в строках таблицы (<HeightInTableRows>) — отдельное свойство от <Height> (высота элемента,
# эмитится generic-ом Emit-Layout ниже). Таблица может нести оба (237 в корпусе).
if ($el.heightInTableRows) { X "$inner<HeightInTableRows>$($el.heightInTableRows)</HeightInTableRows>" }
if ($el.header -eq $false) { X "$inner<Header>false</Header>" }
if ($el.footer -eq $true) { X "$inner<Footer>true</Footer>" }
@@ -3797,7 +3799,7 @@ function Emit-Table {
if ($el.PSObject.Properties["_dynList"] -and $el._dynList) { Emit-DynListTableBlock -el $el -indent $inner }
if ($el.viewStatusLocation) { X "$inner<ViewStatusLocation>$($el.viewStatusLocation)</ViewStatusLocation>" }
if ($el.searchControlLocation) { X "$inner<SearchControlLocation>$($el.searchControlLocation)</SearchControlLocation>" }
Emit-Layout -el $el -indent $inner -skipHeight
Emit-Layout -el $el -indent $inner
if ($el.excludedCommands -and $el.excludedCommands.Count -gt 0) {
X "$inner<CommandSet>"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-compile v1.87 — Compile 1C managed form from JSON or object metadata
# form-compile v1.88 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import copy
@@ -1802,7 +1802,7 @@ KNOWN_KEYS = {
"autofill",
"choiceMode", "initialTreeView", "enableDrag", "enableStartDrag",
"rowSelectionMode", "verticalLines", "horizontalLines",
"rowPictureDataPath", "tableAutofill",
"rowPictureDataPath", "tableAutofill", "heightInTableRows",
# dynamic-list table block
"defaultItem", "useAlternationRowColor", "fileDragMode", "autoRefresh",
"autoRefreshPeriod", "choiceFoldersAndItems", "restoreCurrentRow", "showRoot",
@@ -2707,7 +2707,7 @@ def emit_generic_scalars(lines, el, indent):
def emit_layout(lines, el, indent, skip_height=False, multi_line_default=False):
# Общие layout-свойства — применимы ко всем элементам. Порядок согласован
# с историческим выводом input/label, чтобы не сдвигать существующие снапшоты.
# skip_height: для Table (height → HeightInTableRows, эмитится в emit_table).
# skip_height: подавить <Height> (зарезервирован; Table теперь эмитит <Height> generic-ом + свой <HeightInTableRows>).
# multi_line_default: input без явного autoMaxWidth при multiLine → AutoMaxWidth=false.
emit_common_element_props(lines, el, indent)
if 'autoMaxWidth' in el:
@@ -3468,8 +3468,10 @@ def emit_table(lines, el, name, eid, indent):
# RowFilter — nil-плейсхолдер (ключ присутствует → эмитим)
if 'rowFilter' in el:
lines.append(f'{inner}<RowFilter xsi:nil="true"/>')
if el.get('height'):
lines.append(f'{inner}<HeightInTableRows>{el["height"]}</HeightInTableRows>')
# Высота в строках (<HeightInTableRows>) — отдельное свойство от <Height> (высота элемента,
# эмитится generic-ом emit_layout ниже). Таблица может нести оба (237 в корпусе).
if el.get('heightInTableRows'):
lines.append(f'{inner}<HeightInTableRows>{el["heightInTableRows"]}</HeightInTableRows>')
if el.get('header') is False:
lines.append(f'{inner}<Header>false</Header>')
if el.get('footer') is True:
@@ -3510,7 +3512,7 @@ def emit_table(lines, el, name, eid, indent):
lines.append(f'{inner}<ViewStatusLocation>{el["viewStatusLocation"]}</ViewStatusLocation>')
if el.get('searchControlLocation'):
lines.append(f'{inner}<SearchControlLocation>{el["searchControlLocation"]}</SearchControlLocation>')
emit_layout(lines, el, inner, skip_height=True)
emit_layout(lines, el, inner)
if el.get('excludedCommands'):
lines.append(f'{inner}<CommandSet>')
@@ -1,4 +1,4 @@
# form-decompile v0.63 — Decompile 1C managed Form.xml to JSON DSL (draft)
# form-decompile v0.64 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -756,8 +756,8 @@ function Build-ConditionalAppearance {
}
# Общие layout-свойства → в $obj (симметрично Emit-Layout компилятора).
# Вызывается один раз для любого элемента. Height тут — пиксельная высота
# (<Height>); Table хранит высоту в строках (<HeightInTableRows>) и ловит её сам.
# Вызывается один раз для любого элемента. Height тут — высота элемента (<Height>),
# в т.ч. у Table; высоту в строках (<HeightInTableRows>) Table ловит отдельно в heightInTableRows.
function Add-Layout {
param($obj, $node)
# Общие свойства элемента (любой тип): default/drag/skip
@@ -1480,7 +1480,8 @@ function Decompile-Element {
if ($node.SelectSingleNode("lf:RowFilter", $ns)) { $obj['rowFilter'] = $null }
if ((Get-Child $node 'Header') -eq 'false') { $obj['header'] = $false }
if ((Get-Child $node 'Footer') -eq 'true') { $obj['footer'] = $true }
$htr = Get-Child $node 'HeightInTableRows'; if ($htr) { $obj['height'] = [int]$htr }
# Высота в строках — отдельный ключ heightInTableRows (≠ height = <Height>, его ловит Add-Layout)
$htr = Get-Child $node 'HeightInTableRows'; if ($htr) { $obj['heightInTableRows'] = [int]$htr }
# CommandBarLocation: для дин-список-таблицы компилятор авто-инжектит "None" → инвертируем
# (нет тега → суппресс-маркер ""; "None" → опускаем = авто-дефолт; иначе → захват).
$cbl = Get-Child $node 'CommandBarLocation'
+3 -2
View File
@@ -237,7 +237,7 @@ companion-панели с собственным контентом. Оба не
| Свойство | XML | Значения |
|----------|-----|----------|
| `width` | `<Width>` | число |
| `height` | `<Height>` | число (у `table` `<HeightInTableRows>`, высота в строках) |
| `height` | `<Height>` | число (высота элемента; у `table` — тоже `<Height>`. Высота в строках таблицы — отдельный ключ `heightInTableRows`, см. §table) |
| `horizontalStretch` | `<HorizontalStretch>` | `true`/`false` (эмитится явное значение; отсутствие = дефолт) |
| `verticalStretch` | `<VerticalStretch>` | `true`/`false` (аналогично) |
| `autoMaxWidth` | `<AutoMaxWidth>` | `false` (у `input` при `multiLine` подставляется автоматически) |
@@ -529,7 +529,8 @@ companion-панели с собственным контентом. Оба не
| `verticalLines` / `horizontalLines` | bool | Линии сетки (эмитится явное `false`) |
| `initialTreeView` | string | `ExpandTopLevel`, `ExpandAllLevels`, `NoExpand` |
| `rowsPicture` | string | Картинка строк (`CommonPicture.X`) |
| `height` | int | Высота в строках таблицы |
| `height` | int | Высота элемента таблицы (`<Height>`, как у прочих элементов) |
| `heightInTableRows` | int | Высота в строках (`<HeightInTableRows>`) — отдельное свойство от `height`; таблица может нести оба |
| `header` | bool | Показывать шапку |
| `footer` | bool | Показывать подвал |
| `commandBarLocation` | string | `None`, `Top`, `Bottom`, `Auto` |
@@ -13,8 +13,10 @@
<DataPath>Данные</DataPath>
<TitleLocation>Top</TitleLocation>
<ChangeRowSet>true</ChangeRowSet>
<HeightInTableRows>5</HeightInTableRows>
<ViewStatusLocation>None</ViewStatusLocation>
<SearchControlLocation>None</SearchControlLocation>
<Height>80</Height>
<CommandSet>
<ExcludedCommand>Add</ExcludedCommand>
<ExcludedCommand>Delete</ExcludedCommand>
+1 -1
View File
@@ -16,7 +16,7 @@
"input": {
"title": "Просмотр данных",
"elements": [
{ "table": "Данные", "path": "Данные", "changeRowSet": true, "titleLocation": "top",
{ "table": "Данные", "path": "Данные", "changeRowSet": true, "titleLocation": "top", "height": 80, "heightInTableRows": 5,
"viewStatusLocation": "None", "searchControlLocation": "None",
"excludedCommands": ["Add", "Delete", "MoveUp", "MoveDown"],
"commandBar": { "autofill": false, "children": [