From 720ea05325b3393b0b467e2b8ccc37c2665a6f8e Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 10 Aug 2026 16:48:33 +0300 Subject: [PATCH] =?UTF-8?q?feat(mxl-compile,mxl-decompile):=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=D1=8B=D0=BC=D1=8F=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B1=D0=BB?= =?UTF-8?q?=D0=BE=D0=BA=D0=B8=20=D0=B8=20=D0=BE=D0=B1=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D0=BA=D0=BE=D0=BE=D1=80=D0=B4=D0=B8=D0=BD=D0=B0?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Инкремент A кампании mxl-roundtrip. Блочная форма DSL не выражала больше половины корпуса: у 34% макетов ERP есть строки вне именованных областей, у 21% нет ни одной области типа Rows. Декомпилятор такие строки терял, а на макетах целиком из Rectangle отдавал areas: [] — компилятор отвечал "Required field 'areas' is missing". На пилоте из 40 макетов это 10 отказов из 26. Что сделано: - имя у блока стало необязательным. Блок без имени — просто кусок сетки; именованную область он не создаёт. Отдельный «плоский режим» не нужен: макет без выразимых блоков это один безымянный блок; - namedAreas — именованные области координатами, для всего, что блоком не ложится (не-Rows и пересекающиеся). Тип области НЕ указывается: он выводится из заданных осей, ровно как в ТабличныйДокумент.Область() — только строки дают полосу строк, только колонки полосу колонок, обе оси прямоугольник. Так нельзя написать противоречие вроде type: Rows с колоночными координатами; - диапазон записывается уже существующей грамматикой DSL (как ключи columnWidths): число или "N-M". Список через запятую запрещён — область непрерывна, платформа разрывную не хранит; - прощающим вводом принимается платформенный адрес "R1C1:R2C2" и правило «0 значит 1»; в документацию не вынесено; - декомпилятор перестал пропускать области не-Rows (там стоял безусловный continue) и режет сетку на блоки детерминированно: непересекающиеся Rows задают границы, дыры становятся безымянными блоками, остальное уходит в namedAreas. Отдельно: именованные элементы теперь эмитятся отсортированными по имени. Платформа хранит их именно так — на выборке 541 макета с несколькими элементами иного порядка нет ни разу. Сортировка ординальная и регистронезависимая; Sort-Object по умолчанию сортирует по текущей культуре и на кириллице дал бы другой порядок. Отсюда дрейф 13 снэпшотов — чистая перестановка, диффы симметричны, число элементов не изменилось. На пилоте отказы areas: [] закрыты полностью (10 → 0), цикл переживают 24 макета вместо 14. Оставшиеся 16 отказов — колоночные раскладки, это инкремент B. Правка на ps1, зазеркалена в py; вывод портов и в компиляции, и в декомпиляции совпадает байт в байт. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/mxl-compile/SKILL.md | 1 + .../skills/mxl-compile/reference/dsl-spec.md | 42 ++++- .../mxl-compile/scripts/mxl-compile.ps1 | 132 ++++++++++++++- .../skills/mxl-compile/scripts/mxl-compile.py | 122 ++++++++++++-- .../mxl-decompile/scripts/mxl-decompile.ps1 | 89 ++++++++-- .../mxl-decompile/scripts/mxl-decompile.py | 83 +++++++-- docs/mxl-dsl-spec.md | 42 ++++- .../error-named-area-list-range.json | 12 ++ .../mxl-compile/error-named-area-no-axis.json | 12 ++ .../cases/mxl-compile/named-areas-coords.json | 26 +++ .../snapshots/empty-rows/Template.xml | 20 +-- .../snapshots/format-strings/Template.xml | 20 +-- .../snapshots/merged-cells/Template.xml | 20 +-- .../snapshots/multiple-areas/Template.xml | 12 +- .../snapshots/named-areas-coords/Template.xml | 157 ++++++++++++++++++ .../snapshots/page-a4-landscape/Template.xml | 20 +-- .../parameters-and-templates/Template.xml | 20 +-- .../snapshots/print-form/Template.xml | 40 ++--- .../styles-fonts-borders/Template.xml | 32 ++-- .../snapshots/unnamed-blocks/Template.xml | 140 ++++++++++++++++ .../cases/mxl-compile/unnamed-blocks.json | 23 +++ .../roundtrip-multiple-areas/Template.xml | 20 +-- .../snapshots/areas-and-params/Template.xml | 20 +-- .../mxl-info/snapshots/with-text/Template.xml | 20 +-- .../snapshots/valid-complex/Template.xml | 20 +-- .../snapshots/valid-print-form/Template.xml | 20 +-- 26 files changed, 977 insertions(+), 188 deletions(-) create mode 100644 tests/skills/cases/mxl-compile/error-named-area-list-range.json create mode 100644 tests/skills/cases/mxl-compile/error-named-area-no-axis.json create mode 100644 tests/skills/cases/mxl-compile/named-areas-coords.json create mode 100644 tests/skills/cases/mxl-compile/snapshots/named-areas-coords/Template.xml create mode 100644 tests/skills/cases/mxl-compile/snapshots/unnamed-blocks/Template.xml create mode 100644 tests/skills/cases/mxl-compile/unnamed-blocks.json diff --git a/.claude/skills/mxl-compile/SKILL.md b/.claude/skills/mxl-compile/SKILL.md index b99b755b..369acfd9 100644 --- a/.claude/skills/mxl-compile/SKILL.md +++ b/.claude/skills/mxl-compile/SKILL.md @@ -64,6 +64,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -J - Тип заполнения определяется автоматически: `param` → Parameter, `text` → Text, `template` → Template - `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки) - `empty` в строке — шорткат для N подряд пустых строк (`{ "empty": 3 }` = три `{}`) +- `name` у области необязателен: блок без имени — просто кусок сетки, именованную область он не создаёт - Строку можно писать массивом ячеек — позиция из порядка, `col` не нужен: `"текст"`, `"{Имя}"` — параметр, `">"` — продолжить ячейку слева, `"|"` — сверху, `null` — пропуск колонки ```json diff --git a/.claude/skills/mxl-compile/reference/dsl-spec.md b/.claude/skills/mxl-compile/reference/dsl-spec.md index 8d533cb6..391f846e 100644 --- a/.claude/skills/mxl-compile/reference/dsl-spec.md +++ b/.claude/skills/mxl-compile/reference/dsl-spec.md @@ -79,7 +79,8 @@ | `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"`. Значения: число (абсолют) или `"Nx"` (множитель от defaultWidth, напр. `"2x"`, `"0.5x"`) | | `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) | | `styles` | нет | `{}` | Именованные стили | -| `areas` | да | — | Массив именованных областей (порядок = порядок в документе) | +| `areas` | да | — | Массив блоков строк (порядок = порядок в документе); имя у блока необязательно | +| `namedAreas` | нет | — | Именованные области, заданные координатами (см. ниже) | ## Шрифты (`fonts.`) @@ -110,9 +111,46 @@ | Поле | Обяз. | Описание | |------|:-----:|----------| -| `name` | да | Имя области для `Макет.ПолучитьОбласть("Имя")` | +| `name` | нет | Имя области для `Макет.ПолучитьОбласть("Имя")` | | `rows` | да | Массив строк | +Макет собирается из блоков строк, идущих подряд. Имя на блоке — сокращение: компилятор разворачивает его в именованную область типа «строки» на диапазон блока. **Блок без имени** — просто кусок сетки: так описываются строки, не принадлежащие ни одной именованной области. + +## Именованные области координатами (`namedAreas[]`) + +Для областей, которые блоком не выражаются: не привязанных к диапазону строк (полоса колонок, прямоугольник, ячейка) и пересекающихся с другими. + +| Поле | Обяз. | Описание | +|------|:-----:|----------| +| `name` | да | Имя области | +| `rows` | \* | Строки: число или диапазон `"N-M"`, 1-based | +| `cols` | \* | Колонки: число или диапазон `"N-M"`, 1-based | + +\* Обязательна хотя бы одна из осей. + +**Тип области не указывается** — он следует из того, какие оси заданы, как в `ТабличныйДокумент.Область()`: только строки → полоса строк, только колонки → полоса колонок, обе оси → прямоугольник, одиночные значения по обеим осям → одна ячейка. + +```json +"namedAreas": [ + { "name": "ОбластьПечатиПоВысоте", "rows": "1-48" }, + { "name": "ОбластьПечатиПоШирине", "cols": "1-35" }, + { "name": "HZY", "rows": 9, "cols": "16-17" } +] +``` + +Диапазон — та же грамматика, что у `columnWidths`, но **только** число или `"N-M"`: список через запятую запрещён, область непрерывна. + +Ошибки (stderr, код возврата 1): + +| Условие | Сообщение | +|---------|-----------| +| нет `name` | `namedAreas: 'name' is required: …` | +| не задана ни одна ось | `namedAreas: at least one of 'rows'/'cols' is required: …` | +| список через запятую | `namedAreas: 'cols' must be a single number or range, got list "…": …` | +| диапазон задом наперёд | `namedAreas: 'rows' range is reversed "…": …` | + +Порядок именованных элементов в выходном XML — по имени: так их хранит платформа, компилятор сортирует сам. + ## Строки (`rows[]`) | Поле | По умолч. | Описание | diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 index aa4f4d80..676507cd 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 @@ -1,4 +1,4 @@ -# mxl-compile v1.17 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) +# mxl-compile v1.18 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -978,15 +978,118 @@ foreach ($area in $def.areas) { } $areaEndRow = $globalRow - 1 - $namedItems += @{ - Name = $areaName - BeginRow = $areaStartRow - EndRow = $areaEndRow + # Блок без имени — просто кусок сетки: строки, не принадлежащие ни одной именованной + # области (в корпусе таких дыр 34%, а макетов вовсе без Rows-областей — 21%). + # Имя на блоке — сахар: разворачиваем его в обычную именованную область типа Rows. + if (-not [string]::IsNullOrEmpty($areaName)) { + $namedItems += @{ + Name = $areaName + BeginRow = $areaStartRow + EndRow = $areaEndRow + BeginCol = -1 + EndCol = -1 + } } } $totalRowCount = $globalRow +# 7d-bis. Именованные области, заданные координатами (namedAreas). +# Нужны для всего, что блоком не выражается: области не-Rows и пересекающиеся Rows. +# Ось, которую не указали, означает «вся» — так же устроен ТабличныйДокумент.Область(). + +# Диапазон — та же грамматика, что у columnWidths: число или "N-M". Список через запятую +# для области бессмыслен: область непрерывна, платформа разрывную не хранит. +function ConvertTo-AreaRange { + param($spec, [string]$axis, [string]$where) + if ($null -eq $spec) { return $null } + $s = ([string]$spec).Trim() + if ($s -eq '') { return $null } + if ($s.Contains(',')) { + [Console]::Error.WriteLine("namedAreas: '$axis' must be a single number or range, got list `"$s`": $where") + exit 1 + } + if ($s -match '^(\d+)\s*-\s*(\d+)$') { + $from = [int]$Matches[1]; $to = [int]$Matches[2] + } elseif ($s -match '^(\d+)$') { + $from = [int]$Matches[1]; $to = $from + } else { + [Console]::Error.WriteLine("namedAreas: invalid '$axis' value `"$s`": $where") + exit 1 + } + # Платформа трактует 0 как 1 (см. описание Область()) — принимаем так же. + if ($from -lt 1) { $from = 1 } + if ($to -lt $from) { + [Console]::Error.WriteLine("namedAreas: '$axis' range is reversed `"$s`": $where") + exit 1 + } + return @{ From = $from; To = $to } +} + +# Прощающий ввод: платформенный адрес "R1C1:R2C2" — модель, пишущая код на встроенном +# языке, естественно потянется за ним. В документацию не выносим. +function ConvertFrom-R1C1 { + param([string]$s) + $m = [regex]::Match($s.Trim(), '^R(\d+)(?:C(\d+))?(?::R(\d+)(?:C(\d+))?)?$', 'IgnoreCase') + if (-not $m.Success) { + $m2 = [regex]::Match($s.Trim(), '^C(\d+)(?::C(\d+))?$', 'IgnoreCase') + if (-not $m2.Success) { return $null } + $c1 = [int]$m2.Groups[1].Value + $c2 = if ($m2.Groups[2].Success) { [int]$m2.Groups[2].Value } else { $c1 } + return @{ Rows = $null; Cols = @{ From = $c1; To = $c2 } } + } + $r1 = [int]$m.Groups[1].Value + $r2 = if ($m.Groups[3].Success) { [int]$m.Groups[3].Value } else { $r1 } + $cols = $null + if ($m.Groups[2].Success) { + $c1 = [int]$m.Groups[2].Value + $c2 = if ($m.Groups[4].Success) { [int]$m.Groups[4].Value } else { $c1 } + $cols = @{ From = $c1; To = $c2 } + } + return @{ Rows = @{ From = $r1; To = $r2 }; Cols = $cols } +} + +if ($def.namedAreas) { + $naIdx = 0 + foreach ($na in $def.namedAreas) { + $naIdx++ + $naName = "$($na.name)" + $where = "namedAreas[$naIdx]" + $(if ($naName) { " `"$naName`"" } else { '' }) + if ([string]::IsNullOrEmpty($naName)) { + [Console]::Error.WriteLine("namedAreas: 'name' is required: $where") + exit 1 + } + $rows = ConvertTo-AreaRange $na.rows 'rows' $where + $cols = ConvertTo-AreaRange $na.cols 'cols' $where + if (-not $rows -and -not $cols) { + $addr = $null + foreach ($k in 'area', 'at', 'address') { + if ($na.PSObject.Properties[$k] -and "$($na.$k)" -ne '') { $addr = "$($na.$k)"; break } + } + if ($addr) { + $parsed = ConvertFrom-R1C1 $addr + if ($null -eq $parsed) { + [Console]::Error.WriteLine("namedAreas: invalid address `"$addr`": $where") + exit 1 + } + $rows = $parsed.Rows; $cols = $parsed.Cols + } + } + if (-not $rows -and -not $cols) { + [Console]::Error.WriteLine("namedAreas: at least one of 'rows'/'cols' is required: $where") + exit 1 + } + # DSL 1-based, XML 0-based; отсутствующая ось помечается -1. + $namedItems += @{ + Name = $naName + BeginRow = if ($rows) { $rows.From - 1 } else { -1 } + EndRow = if ($rows) { $rows.To - 1 } else { -1 } + BeginCol = if ($cols) { $cols.From - 1 } else { -1 } + EndCol = if ($cols) { $cols.To - 1 } else { -1 } + } + } +} + # 7e. Scalar metadata X "`ttrue" X "`t$defaultFormatIndex" @@ -1004,15 +1107,26 @@ foreach ($m in $merges) { } # 7g. Named items -foreach ($ni in $namedItems) { +# Платформа хранит именованные элементы ОТСОРТИРОВАННЫМИ по имени: на выборке 541 макета +# с несколькими элементами иного порядка нет ни разу. Сортировка регистронезависимая и +# ординальная — Sort-Object брать нельзя, он сортирует по текущей культуре и на кириллице +# даст другой порядок. NB: имён с «ё» в выборке не встретилось, этот случай не проверен. +$sortedNamedItems = @($namedItems | Sort-Object -Property @{ Expression = { $_.Name.ToLowerInvariant() } } ` + -CaseSensitive) +foreach ($ni in $sortedNamedItems) { + # Тип области выводится из указанных осей, как в ТабличныйДокумент.Область(): + # нет колонок → полоса строк, нет строк → полоса колонок, обе → прямоугольник. + $hasRows = $ni.BeginRow -ge 0 + $hasCols = $ni.BeginCol -ge 0 + $type = if ($hasRows -and $hasCols) { 'Rectangle' } elseif ($hasCols) { 'Columns' } else { 'Rows' } X "`t" X "`t`t$($ni.Name)" X "`t`t" - X "`t`t`tRows" + X "`t`t`t$type" X "`t`t`t$($ni.BeginRow)" X "`t`t`t$($ni.EndRow)" - X "`t`t`t-1" - X "`t`t`t-1" + X "`t`t`t$($ni.BeginCol)" + X "`t`t`t$($ni.EndCol)" X "`t`t" X "`t" } diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.py b/.claude/skills/mxl-compile/scripts/mxl-compile.py index 28f3a4c5..648abed9 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.py +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python3 -# mxl-compile v1.17 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) +#!/usr/bin/env python3 +# mxl-compile v1.18 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -976,14 +976,105 @@ def main(): global_row += 1 area_end_row = global_row - 1 - named_items.append({ - 'Name': area_name, - 'BeginRow': area_start_row, - 'EndRow': area_end_row, - }) + # Блок без имени — просто кусок сетки: строки, не принадлежащие ни одной именованной + # области (в корпусе таких дыр 34%, а макетов вовсе без Rows-областей — 21%). + # Имя на блоке — сахар: разворачиваем его в обычную именованную область типа Rows. + if area_name: + named_items.append({ + 'Name': area_name, + 'BeginRow': area_start_row, + 'EndRow': area_end_row, + 'BeginCol': -1, + 'EndCol': -1, + }) total_row_count = global_row + # 7d-bis. Именованные области, заданные координатами (namedAreas). + # Нужны для всего, что блоком не выражается: области не-Rows и пересекающиеся Rows. + # Ось, которую не указали, означает «вся» — так же устроен ТабличныйДокумент.Область(). + + def to_area_range(spec, axis, where): + """Диапазон — та же грамматика, что у columnWidths: число или "N-M". Список через + запятую для области бессмыслен: область непрерывна, платформа разрывную не хранит.""" + if spec is None: + return None + s = str(spec).strip() + if s == '': + return None + if ',' in s: + print(f'namedAreas: \'{axis}\' must be a single number or range, got list "{s}": {where}', + file=sys.stderr) + sys.exit(1) + m = re.match(r'^(\d+)\s*-\s*(\d+)$', s) + if m: + frm, to = int(m.group(1)), int(m.group(2)) + elif re.match(r'^\d+$', s): + frm = to = int(s) + else: + print(f'namedAreas: invalid \'{axis}\' value "{s}": {where}', file=sys.stderr) + sys.exit(1) + # Платформа трактует 0 как 1 (см. описание Область()) — принимаем так же. + if frm < 1: + frm = 1 + if to < frm: + print(f'namedAreas: \'{axis}\' range is reversed "{s}": {where}', file=sys.stderr) + sys.exit(1) + return (frm, to) + + def from_r1c1(s): + """Прощающий ввод: платформенный адрес "R1C1:R2C2" — модель, пишущая код на встроенном + языке, естественно потянется за ним. В документацию не выносим.""" + s = s.strip() + m = re.match(r'^R(\d+)(?:C(\d+))?(?::R(\d+)(?:C(\d+))?)?$', s, re.IGNORECASE) + if not m: + m2 = re.match(r'^C(\d+)(?::C(\d+))?$', s, re.IGNORECASE) + if not m2: + return None + c1 = int(m2.group(1)) + c2 = int(m2.group(2)) if m2.group(2) else c1 + return (None, (c1, c2)) + r1 = int(m.group(1)) + r2 = int(m.group(3)) if m.group(3) else r1 + cols = None + if m.group(2): + c1 = int(m.group(2)) + c2 = int(m.group(4)) if m.group(4) else c1 + cols = (c1, c2) + return ((r1, r2), cols) + + for na_idx, na in enumerate(defn.get('namedAreas') or [], start=1): + na_name = str(na.get('name') or '') + where = f'namedAreas[{na_idx}]' + (f' "{na_name}"' if na_name else '') + if not na_name: + print(f'namedAreas: \'name\' is required: {where}', file=sys.stderr) + sys.exit(1) + rows = to_area_range(na.get('rows'), 'rows', where) + cols = to_area_range(na.get('cols'), 'cols', where) + if not rows and not cols: + addr = None + for k in ('area', 'at', 'address'): + if na.get(k): + addr = str(na.get(k)) + break + if addr: + parsed = from_r1c1(addr) + if parsed is None: + print(f'namedAreas: invalid address "{addr}": {where}', file=sys.stderr) + sys.exit(1) + rows, cols = parsed + if not rows and not cols: + print(f'namedAreas: at least one of \'rows\'/\'cols\' is required: {where}', file=sys.stderr) + sys.exit(1) + # DSL 1-based, XML 0-based; отсутствующая ось помечается -1. + named_items.append({ + 'Name': na_name, + 'BeginRow': rows[0] - 1 if rows else -1, + 'EndRow': rows[1] - 1 if rows else -1, + 'BeginCol': cols[0] - 1 if cols else -1, + 'EndCol': cols[1] - 1 if cols else -1, + }) + # 7e. Scalar metadata lines.append(f'\ttrue') lines.append(f'\t{default_format_index}') @@ -1001,15 +1092,24 @@ def main(): lines.append('\t') # 7g. Named items - for ni in named_items: + # Платформа хранит именованные элементы ОТСОРТИРОВАННЫМИ по имени: на выборке 541 макета + # с несколькими элементами иного порядка нет ни разу. Сортировка регистронезависимая и + # ординальная (в ps1 поэтому нельзя Sort-Object — он сортирует по текущей культуре). + # NB: имён с «ё» в выборке не встретилось, этот случай не проверен. + for ni in sorted(named_items, key=lambda x: str(x['Name']).lower()): + # Тип области выводится из указанных осей, как в ТабличныйДокумент.Область(): + # нет колонок → полоса строк, нет строк → полоса колонок, обе → прямоугольник. + has_rows = int(ni['BeginRow']) >= 0 + has_cols = int(ni.get('BeginCol', -1)) >= 0 + area_type = 'Rectangle' if (has_rows and has_cols) else ('Columns' if has_cols else 'Rows') lines.append('\t') lines.append(f'\t\t{ni["Name"]}') lines.append('\t\t') - lines.append('\t\t\tRows') + lines.append(f'\t\t\t{area_type}') lines.append(f'\t\t\t{ni["BeginRow"]}') lines.append(f'\t\t\t{ni["EndRow"]}') - lines.append('\t\t\t-1') - lines.append('\t\t\t-1') + lines.append(f'\t\t\t{ni.get("BeginCol", -1)}') + lines.append(f'\t\t\t{ni.get("EndCol", -1)}') lines.append('\t\t') lines.append('\t') diff --git a/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 b/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 index 20445777..7aa7c684 100644 --- a/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 +++ b/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 @@ -1,4 +1,4 @@ -# mxl-decompile v1.3 — Decompile 1C spreadsheet to JSON +# mxl-decompile v1.4 — Decompile 1C spreadsheet to JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -161,19 +161,29 @@ foreach ($mNode in $root.SelectNodes("d:merge", $ns)) { # --- 7. Extract named items --- +# Захватываем области ВСЕХ типов. Раньше здесь стоял `if ($areaType -ne "Rows") { continue }`, +# из-за чего терялись Rectangle и Columns — а они есть у 61% макетов корпуса. $namedAreas = @() foreach ($niNode in $root.SelectNodes("d:namedItem", $ns)) { $xsiType = $niNode.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance") if ($xsiType -ne "NamedItemCells") { continue } $areaNode = $niNode.SelectSingleNode("d:area", $ns) - $areaType = $areaNode.SelectSingleNode("d:type", $ns).InnerText - if ($areaType -ne "Rows") { continue } + if (-not $areaNode) { continue } + $getCoord = { + param([string]$tag) + $n = $areaNode.SelectSingleNode("d:$tag", $ns) + if ($n) { return [int]$n.InnerText } + return -1 + } $namedAreas += @{ Name = $niNode.SelectSingleNode("d:name", $ns).InnerText - BeginRow = [int]$areaNode.SelectSingleNode("d:beginRow", $ns).InnerText - EndRow = [int]$areaNode.SelectSingleNode("d:endRow", $ns).InnerText + Type = $areaNode.SelectSingleNode("d:type", $ns).InnerText + BeginRow = & $getCoord 'beginRow' + EndRow = & $getCoord 'endRow' + BeginCol = & $getCoord 'beginColumn' + EndCol = & $getCoord 'endColumn' } } @@ -434,9 +444,50 @@ function Get-StyleName { # --- 12. Build areas --- +# Сетка нарезается на блоки: непересекающиеся области типа Rows задают границы, строки вне +# них становятся БЕЗЫМЯННЫМИ блоками. Раньше строки вне областей просто терялись — в корпусе +# такие дыры у 34% макетов, а макетов вовсе без Rows-областей 21%. +# Всё, что блоком не выражается (области не-Rows и пересекающиеся Rows), уходит в namedAreas +# координатами. Правило детерминированное — иначе раундтрип поехал бы. + +$maxRowIdx = -1 +foreach ($k in $rowData.Keys) { if ([int]$k -gt $maxRowIdx) { $maxRowIdx = [int]$k } } + +$blockAreas = @() +$overlayAreas = @() +$claimed = @{} # строка → занята блоком +foreach ($a in @($namedAreas | Sort-Object @{ Expression = { $_.BeginRow } }, @{ Expression = { $_.EndRow } })) { + $fitsBlock = ($a.Type -eq 'Rows' -and $a.BeginRow -ge 0 -and $a.EndRow -ge $a.BeginRow) + if ($fitsBlock) { + for ($r = $a.BeginRow; $r -le $a.EndRow; $r++) { + if ($claimed.ContainsKey($r)) { $fitsBlock = $false; break } + } + } + if ($fitsBlock) { + for ($r = $a.BeginRow; $r -le $a.EndRow; $r++) { $claimed[$r] = $true } + $blockAreas += $a + } else { + $overlayAreas += $a + } +} + +# Блоки в порядке строк + безымянные заполнители дыр. +$blocks = @() +$cursor = 0 +foreach ($a in @($blockAreas | Sort-Object @{ Expression = { $_.BeginRow } })) { + if ($a.BeginRow -gt $cursor) { + $blocks += @{ Name = $null; BeginRow = $cursor; EndRow = $a.BeginRow - 1 } + } + $blocks += @{ Name = $a.Name; BeginRow = $a.BeginRow; EndRow = $a.EndRow } + $cursor = $a.EndRow + 1 +} +if ($cursor -le $maxRowIdx) { + $blocks += @{ Name = $null; BeginRow = $cursor; EndRow = $maxRowIdx } +} + $dslAreas = @() -foreach ($area in $namedAreas) { +foreach ($area in $blocks) { $areaRows = @() for ($globalRow = $area.BeginRow; $globalRow -le $area.EndRow; $globalRow++) { @@ -557,10 +608,11 @@ foreach ($area in $namedAreas) { else { $compressedRows += [ordered]@{ empty = $emptyRun } } } - $dslAreas += [ordered]@{ - name = $area.Name - rows = [array]$compressedRows - } + $dslBlock = [ordered]@{} + # Безымянный блок — просто кусок сетки, ключ name у него не пишем. + if (-not [string]::IsNullOrEmpty($area.Name)) { $dslBlock["name"] = $area.Name } + $dslBlock["rows"] = [array]$compressedRows + $dslAreas += $dslBlock } # --- 13. Compress columnWidths --- @@ -632,6 +684,23 @@ $result["fonts"] = $fontsOut $result["styles"] = $styleDefs $result["areas"] = [array]$dslAreas +# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится +# из указанных осей (как в ТабличныйДокумент.Область()). DSL 1-based, XML 0-based. +if ($overlayAreas.Count -gt 0) { + $naOut = @() + foreach ($a in $overlayAreas) { + $entry = [ordered]@{ name = $a.Name } + if ($a.BeginRow -ge 0) { + $entry["rows"] = if ($a.EndRow -gt $a.BeginRow) { "$($a.BeginRow + 1)-$($a.EndRow + 1)" } else { $a.BeginRow + 1 } + } + if ($a.BeginCol -ge 0) { + $entry["cols"] = if ($a.EndCol -gt $a.BeginCol) { "$($a.BeginCol + 1)-$($a.EndCol + 1)" } else { $a.BeginCol + 1 } + } + $naOut += $entry + } + $result["namedAreas"] = [array]$naOut +} + # --- 16. Convert to JSON --- # Custom JSON serializer — компактный, 2-пробельный indent, массивы примитивов inline. diff --git a/.claude/skills/mxl-decompile/scripts/mxl-decompile.py b/.claude/skills/mxl-decompile/scripts/mxl-decompile.py index e9b9e31f..330cbffd 100644 --- a/.claude/skills/mxl-decompile/scripts/mxl-decompile.py +++ b/.claude/skills/mxl-decompile/scripts/mxl-decompile.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python3 -# mxl-decompile v1.3 — Decompile 1C spreadsheet to JSON +#!/usr/bin/env python3 +# mxl-decompile v1.4 — Decompile 1C spreadsheet to JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -337,6 +337,8 @@ def main(): # --- 7. Extract named items --- + # Захватываем области ВСЕХ типов. Раньше здесь стоял `if area_type != "Rows": continue`, + # из-за чего терялись Rectangle и Columns — а они есть у 61% макетов корпуса. named_areas = [] for ni_node in findall(root, "d:namedItem"): xsi_type = ni_node.get(f"{{{XSI_NS}}}type", "") @@ -344,15 +346,20 @@ def main(): continue area_node = find(ni_node, "d:area") - area_type_node = find(area_node, "d:type") - area_type = text_of(area_type_node) or "" - if area_type != "Rows": + if area_node is None: continue + def coord(tag, node=area_node): + n = find(node, "d:" + tag) + return int_of(n) if n is not None else -1 + named_areas.append({ "Name": text_of(find(ni_node, "d:name")) or "", - "BeginRow": int_of(find(area_node, "d:beginRow")), - "EndRow": int_of(find(area_node, "d:endRow")), + "Type": text_of(find(area_node, "d:type")) or "", + "BeginRow": coord("beginRow"), + "EndRow": coord("endRow"), + "BeginCol": coord("beginColumn"), + "EndCol": coord("endColumn"), }) # --- 8. Extract rows --- @@ -630,9 +637,44 @@ def main(): # --- 12. Build areas --- + # Сетка нарезается на блоки: непересекающиеся области типа Rows задают границы, строки вне + # них становятся БЕЗЫМЯННЫМИ блоками. Раньше строки вне областей просто терялись — в корпусе + # такие дыры у 34% макетов, а макетов вовсе без Rows-областей 21%. + # Всё, что блоком не выражается (области не-Rows и пересекающиеся Rows), уходит в namedAreas + # координатами. Правило детерминированное — иначе раундтрип поехал бы. + + max_row_idx = max(row_data.keys()) if row_data else -1 + + block_areas = [] + overlay_areas = [] + claimed = set() + for a in sorted(named_areas, key=lambda x: (x["BeginRow"], x["EndRow"])): + fits = a["Type"] == "Rows" and a["BeginRow"] >= 0 and a["EndRow"] >= a["BeginRow"] + if fits: + for r in range(a["BeginRow"], a["EndRow"] + 1): + if r in claimed: + fits = False + break + if fits: + claimed.update(range(a["BeginRow"], a["EndRow"] + 1)) + block_areas.append(a) + else: + overlay_areas.append(a) + + # Блоки в порядке строк + безымянные заполнители дыр. + blocks = [] + cursor = 0 + for a in sorted(block_areas, key=lambda x: x["BeginRow"]): + if a["BeginRow"] > cursor: + blocks.append({"Name": None, "BeginRow": cursor, "EndRow": a["BeginRow"] - 1}) + blocks.append({"Name": a["Name"], "BeginRow": a["BeginRow"], "EndRow": a["EndRow"]}) + cursor = a["EndRow"] + 1 + if cursor <= max_row_idx: + blocks.append({"Name": None, "BeginRow": cursor, "EndRow": max_row_idx}) + dsl_areas = [] - for area in named_areas: + for area in blocks: area_rows = [] for global_row in range(area["BeginRow"], area["EndRow"] + 1): @@ -746,10 +788,12 @@ def main(): else: compressed_rows.append(OrderedDict([("empty", empty_run)])) - dsl_areas.append(OrderedDict([ - ("name", area["Name"]), - ("rows", compressed_rows), - ])) + dsl_block = OrderedDict() + # Безымянный блок — просто кусок сетки, ключ name у него не пишем. + if area["Name"]: + dsl_block["name"] = area["Name"] + dsl_block["rows"] = compressed_rows + dsl_areas.append(dsl_block) # --- 13. Compress columnWidths --- @@ -833,6 +877,21 @@ def main(): result["styles"] = style_defs result["areas"] = dsl_areas + # Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится + # из указанных осей (как в ТабличныйДокумент.Область()). DSL 1-based, XML 0-based. + if overlay_areas: + na_out = [] + for a in overlay_areas: + entry = OrderedDict([("name", a["Name"])]) + if a["BeginRow"] >= 0: + entry["rows"] = (f'{a["BeginRow"] + 1}-{a["EndRow"] + 1}' + if a["EndRow"] > a["BeginRow"] else a["BeginRow"] + 1) + if a["BeginCol"] >= 0: + entry["cols"] = (f'{a["BeginCol"] + 1}-{a["EndCol"] + 1}' + if a["EndCol"] > a["BeginCol"] else a["BeginCol"] + 1) + na_out.append(entry) + result["namedAreas"] = na_out + # --- 16. Convert to JSON --- json_str = convert_to_compact_json(result) diff --git a/docs/mxl-dsl-spec.md b/docs/mxl-dsl-spec.md index 52c9cff6..54eba8ea 100644 --- a/docs/mxl-dsl-spec.md +++ b/docs/mxl-dsl-spec.md @@ -79,7 +79,8 @@ | `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"`. Значения: число (абсолют) или `"Nx"` (множитель от defaultWidth, напр. `"2x"`, `"0.5x"`) | | `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) | | `styles` | нет | `{}` | Именованные стили | -| `areas` | да | — | Массив именованных областей (порядок = порядок в документе) | +| `areas` | да | — | Массив блоков строк (порядок = порядок в документе); имя у блока необязательно | +| `namedAreas` | нет | — | Именованные области, заданные координатами (см. ниже) | ## Шрифты (`fonts.`) @@ -110,9 +111,46 @@ | Поле | Обяз. | Описание | |------|:-----:|----------| -| `name` | да | Имя области для `Макет.ПолучитьОбласть("Имя")` | +| `name` | нет | Имя области для `Макет.ПолучитьОбласть("Имя")` | | `rows` | да | Массив строк | +Макет собирается из блоков строк, идущих подряд. Имя на блоке — сокращение: компилятор разворачивает его в именованную область типа «строки» на диапазон блока. **Блок без имени** — просто кусок сетки: так описываются строки, не принадлежащие ни одной именованной области. + +## Именованные области координатами (`namedAreas[]`) + +Для областей, которые блоком не выражаются: не привязанных к диапазону строк (полоса колонок, прямоугольник, ячейка) и пересекающихся с другими. + +| Поле | Обяз. | Описание | +|------|:-----:|----------| +| `name` | да | Имя области | +| `rows` | \* | Строки: число или диапазон `"N-M"`, 1-based | +| `cols` | \* | Колонки: число или диапазон `"N-M"`, 1-based | + +\* Обязательна хотя бы одна из осей. + +**Тип области не указывается** — он следует из того, какие оси заданы, как в `ТабличныйДокумент.Область()`: только строки → полоса строк, только колонки → полоса колонок, обе оси → прямоугольник, одиночные значения по обеим осям → одна ячейка. + +```json +"namedAreas": [ + { "name": "ОбластьПечатиПоВысоте", "rows": "1-48" }, + { "name": "ОбластьПечатиПоШирине", "cols": "1-35" }, + { "name": "HZY", "rows": 9, "cols": "16-17" } +] +``` + +Диапазон — та же грамматика, что у `columnWidths`, но **только** число или `"N-M"`: список через запятую запрещён, область непрерывна. + +Ошибки (stderr, код возврата 1): + +| Условие | Сообщение | +|---------|-----------| +| нет `name` | `namedAreas: 'name' is required: …` | +| не задана ни одна ось | `namedAreas: at least one of 'rows'/'cols' is required: …` | +| список через запятую | `namedAreas: 'cols' must be a single number or range, got list "…": …` | +| диапазон задом наперёд | `namedAreas: 'rows' range is reversed "…": …` | + +Порядок именованных элементов в выходном XML — по имени: так их хранит платформа, компилятор сортирует сам. + ## Строки (`rows[]`) | Поле | По умолч. | Описание | diff --git a/tests/skills/cases/mxl-compile/error-named-area-list-range.json b/tests/skills/cases/mxl-compile/error-named-area-list-range.json new file mode 100644 index 00000000..44e0af5c --- /dev/null +++ b/tests/skills/cases/mxl-compile/error-named-area-list-range.json @@ -0,0 +1,12 @@ +{ + "name": "Ошибка: список через запятую в координатах области", + "input": { + "columns": 5, + "areas": [{ "rows": [["А", "Б", "В", "Г", "Д"]] }], + "namedAreas": [{ "name": "Разрывная", "cols": "1,3,5" }] + }, + "params": { + "outputPath": "Template.xml" + }, + "expectError": "namedAreas: 'cols' must be a single number or range, got list \"1,3,5\": namedAreas[1] \"Разрывная\"" +} diff --git a/tests/skills/cases/mxl-compile/error-named-area-no-axis.json b/tests/skills/cases/mxl-compile/error-named-area-no-axis.json new file mode 100644 index 00000000..a054969d --- /dev/null +++ b/tests/skills/cases/mxl-compile/error-named-area-no-axis.json @@ -0,0 +1,12 @@ +{ + "name": "Ошибка: у именованной области не задано ни одной оси", + "input": { + "columns": 3, + "areas": [{ "rows": [["А", "Б", "В"]] }], + "namedAreas": [{ "name": "Пустая" }] + }, + "params": { + "outputPath": "Template.xml" + }, + "expectError": "namedAreas: at least one of 'rows'/'cols' is required: namedAreas[1] \"Пустая\"" +} diff --git a/tests/skills/cases/mxl-compile/named-areas-coords.json b/tests/skills/cases/mxl-compile/named-areas-coords.json new file mode 100644 index 00000000..39e284f9 --- /dev/null +++ b/tests/skills/cases/mxl-compile/named-areas-coords.json @@ -0,0 +1,26 @@ +{ + "name": "Именованные области координатами: тип выводится из осей", + "input": { + "columns": 4, + "areas": [ + { "rows": [["А", "Б", "В", "Г"], ["{П1}", "{П2}", "{П3}", "{П4}"]] } + ], + "namedAreas": [ + { "name": "ПолосаСтрок", "rows": "1-2" }, + { "name": "ПолосаКолонок", "cols": "2-4" }, + { "name": "Прямоугольник", "rows": 2, "cols": "2-3" }, + { "name": "Ячейка", "rows": 1, "cols": 1 } + ] + }, + "params": { + "outputPath": "Template.xml" + }, + "validatePath": "Template.xml", + "expect": { + "files": ["Template.xml"], + "fileContains": { + "file": "Template.xml", + "text": ["Rows", "Columns", "Rectangle"] + } + } +} diff --git a/tests/skills/cases/mxl-compile/snapshots/empty-rows/Template.xml b/tests/skills/cases/mxl-compile/snapshots/empty-rows/Template.xml index f69fe4b0..46edb9b6 100644 --- a/tests/skills/cases/mxl-compile/snapshots/empty-rows/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/empty-rows/Template.xml @@ -76,16 +76,6 @@ 0 2 - - Заголовок - - Rows - 0 - 2 - -1 - -1 - - Данные @@ -96,6 +86,16 @@ -1 + + Заголовок + + Rows + 0 + 2 + -1 + -1 + + Solid diff --git a/tests/skills/cases/mxl-compile/snapshots/format-strings/Template.xml b/tests/skills/cases/mxl-compile/snapshots/format-strings/Template.xml index 163976ea..2a8be3ed 100644 --- a/tests/skills/cases/mxl-compile/snapshots/format-strings/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/format-strings/Template.xml @@ -102,16 +102,6 @@ 1 2 2 - - ШапкаТаблицы - - Rows - 0 - 0 - -1 - -1 - - Строка @@ -122,6 +112,16 @@ -1 + + ШапкаТаблицы + + Rows + 0 + 0 + -1 + -1 + + Solid diff --git a/tests/skills/cases/mxl-compile/snapshots/merged-cells/Template.xml b/tests/skills/cases/mxl-compile/snapshots/merged-cells/Template.xml index 3da6daa6..e9746f5a 100644 --- a/tests/skills/cases/mxl-compile/snapshots/merged-cells/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/merged-cells/Template.xml @@ -136,16 +136,6 @@ 1 0 - - Шапка - - Rows - 0 - 1 - -1 - -1 - - Строка @@ -156,6 +146,16 @@ -1 + + Шапка + + Rows + 0 + 1 + -1 + -1 + + Solid diff --git a/tests/skills/cases/mxl-compile/snapshots/multiple-areas/Template.xml b/tests/skills/cases/mxl-compile/snapshots/multiple-areas/Template.xml index c148f90e..fd0d3713 100644 --- a/tests/skills/cases/mxl-compile/snapshots/multiple-areas/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/multiple-areas/Template.xml @@ -156,11 +156,11 @@ - ШапкаТаблицы + Итого Rows - 1 - 1 + 3 + 3 -1 -1 @@ -176,11 +176,11 @@ - Итого + ШапкаТаблицы Rows - 3 - 3 + 1 + 1 -1 -1 diff --git a/tests/skills/cases/mxl-compile/snapshots/named-areas-coords/Template.xml b/tests/skills/cases/mxl-compile/snapshots/named-areas-coords/Template.xml new file mode 100644 index 00000000..191d4cc7 --- /dev/null +++ b/tests/skills/cases/mxl-compile/snapshots/named-areas-coords/Template.xml @@ -0,0 +1,157 @@ + + + + ru + ru + + ru + Русский + Русский + + + + 4 + + + 0 + + + 0 + + 2 + + + ru + А + + + + + + 1 + + 2 + + + ru + Б + + + + + + 2 + + 2 + + + ru + В + + + + + + 3 + + 2 + + + ru + Г + + + + + + + + 1 + + + 0 + + 3 + П1 + + + + 1 + + 3 + П2 + + + + 2 + + 3 + П3 + + + + 3 + + 3 + П4 + + + + + true + 1 + 2 + 2 + + ПолосаКолонок + + Columns + -1 + -1 + 1 + 3 + + + + ПолосаСтрок + + Rows + 0 + 1 + -1 + -1 + + + + Прямоугольник + + Rectangle + 1 + 1 + 1 + 2 + + + + Ячейка + + Rectangle + 0 + 0 + 0 + 0 + + + + + 10 + + + 0 + Text + + + 0 + Parameter + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/snapshots/page-a4-landscape/Template.xml b/tests/skills/cases/mxl-compile/snapshots/page-a4-landscape/Template.xml index f608c5d2..fc1211a7 100644 --- a/tests/skills/cases/mxl-compile/snapshots/page-a4-landscape/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/page-a4-landscape/Template.xml @@ -151,16 +151,6 @@ 1 2 2 - - ШапкаТаблицы - - Rows - 0 - 0 - -1 - -1 - - Строка @@ -171,6 +161,16 @@ -1 + + ШапкаТаблицы + + Rows + 0 + 0 + -1 + -1 + + Solid diff --git a/tests/skills/cases/mxl-compile/snapshots/parameters-and-templates/Template.xml b/tests/skills/cases/mxl-compile/snapshots/parameters-and-templates/Template.xml index fa8eb5b3..c9e3636b 100644 --- a/tests/skills/cases/mxl-compile/snapshots/parameters-and-templates/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/parameters-and-templates/Template.xml @@ -110,16 +110,6 @@ 1 1 - - Шапка - - Rows - 0 - 1 - -1 - -1 - - Тело @@ -130,6 +120,16 @@ -1 + + Шапка + + Rows + 0 + 1 + -1 + -1 + + Solid diff --git a/tests/skills/cases/mxl-compile/snapshots/print-form/Template.xml b/tests/skills/cases/mxl-compile/snapshots/print-form/Template.xml index 83c98449..47589ded 100644 --- a/tests/skills/cases/mxl-compile/snapshots/print-form/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/print-form/Template.xml @@ -501,26 +501,6 @@ -1 - - ШапкаТаблицы - - Rows - 4 - 4 - -1 - -1 - - - - Строка - - Rows - 5 - 5 - -1 - -1 - - Итого @@ -541,6 +521,26 @@ -1 + + Строка + + Rows + 5 + 5 + -1 + -1 + + + + ШапкаТаблицы + + Rows + 4 + 4 + -1 + -1 + + Solid diff --git a/tests/skills/cases/mxl-compile/snapshots/styles-fonts-borders/Template.xml b/tests/skills/cases/mxl-compile/snapshots/styles-fonts-borders/Template.xml index 79f52717..69a32bc1 100644 --- a/tests/skills/cases/mxl-compile/snapshots/styles-fonts-borders/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/styles-fonts-borders/Template.xml @@ -234,6 +234,16 @@ -1 + + Итого + + Rows + 4 + 4 + -1 + -1 + + Подпись @@ -245,11 +255,11 @@ - ШапкаТаблицы + Примечание Rows - 2 - 2 + 5 + 5 -1 -1 @@ -265,21 +275,11 @@ - Итого + ШапкаТаблицы Rows - 4 - 4 - -1 - -1 - - - - Примечание - - Rows - 5 - 5 + 2 + 2 -1 -1 diff --git a/tests/skills/cases/mxl-compile/snapshots/unnamed-blocks/Template.xml b/tests/skills/cases/mxl-compile/snapshots/unnamed-blocks/Template.xml new file mode 100644 index 00000000..caadd1a9 --- /dev/null +++ b/tests/skills/cases/mxl-compile/snapshots/unnamed-blocks/Template.xml @@ -0,0 +1,140 @@ + + + + ru + ru + + ru + Русский + Русский + + + + 3 + + + 0 + + + 0 + + 2 + + + ru + А + + + + + + 1 + + 2 + + + ru + Б + + + + + + 2 + + 2 + + + ru + В + + + + + + + + 1 + + + 0 + + 2 + + + ru + строки вне областей + + + + + + + + 2 + + + 0 + + 3 + П1 + + + + 1 + + 3 + П2 + + + + 2 + + 3 + П3 + + + + + true + 1 + 3 + 3 + + 1 + 0 + 2 + + + Строка + + Rows + 2 + 2 + -1 + -1 + + + + Шапка + + Rows + 0 + 0 + -1 + -1 + + + + + 10 + + + 0 + Text + + + 0 + Parameter + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/unnamed-blocks.json b/tests/skills/cases/mxl-compile/unnamed-blocks.json new file mode 100644 index 00000000..0f2c8c6b --- /dev/null +++ b/tests/skills/cases/mxl-compile/unnamed-blocks.json @@ -0,0 +1,23 @@ +{ + "name": "Блок без имени не порождает именованную область", + "input": { + "columns": 3, + "areas": [ + { "name": "Шапка", "rows": [["А", "Б", "В"]] }, + { "rows": [["строки вне областей", ">", ">"]] }, + { "name": "Строка", "rows": [["{П1}", "{П2}", "{П3}"]] } + ] + }, + "params": { + "outputPath": "Template.xml" + }, + "validatePath": "Template.xml", + "expect": { + "files": ["Template.xml"], + "stdoutContains": "Areas: 2", + "fileContains": { + "file": "Template.xml", + "text": ["Шапка", "Строка", "строки вне областей"] + } + } +} diff --git a/tests/skills/cases/mxl-decompile/snapshots/roundtrip-multiple-areas/Template.xml b/tests/skills/cases/mxl-decompile/snapshots/roundtrip-multiple-areas/Template.xml index 4b18dbc4..94d3f45f 100644 --- a/tests/skills/cases/mxl-decompile/snapshots/roundtrip-multiple-areas/Template.xml +++ b/tests/skills/cases/mxl-decompile/snapshots/roundtrip-multiple-areas/Template.xml @@ -101,16 +101,6 @@ -1 - - Строка - - Rows - 1 - 1 - -1 - -1 - - Итого @@ -121,6 +111,16 @@ -1 + + Строка + + Rows + 1 + 1 + -1 + -1 + + Solid diff --git a/tests/skills/cases/mxl-info/snapshots/areas-and-params/Template.xml b/tests/skills/cases/mxl-info/snapshots/areas-and-params/Template.xml index be04f3e7..0b602866 100644 --- a/tests/skills/cases/mxl-info/snapshots/areas-and-params/Template.xml +++ b/tests/skills/cases/mxl-info/snapshots/areas-and-params/Template.xml @@ -143,6 +143,16 @@ -1 + + Итого + + Rows + 3 + 3 + -1 + -1 + + Поставщик @@ -163,16 +173,6 @@ -1 - - Итого - - Rows - 3 - 3 - -1 - -1 - - Solid diff --git a/tests/skills/cases/mxl-info/snapshots/with-text/Template.xml b/tests/skills/cases/mxl-info/snapshots/with-text/Template.xml index 5c51fbf4..7c5b1349 100644 --- a/tests/skills/cases/mxl-info/snapshots/with-text/Template.xml +++ b/tests/skills/cases/mxl-info/snapshots/with-text/Template.xml @@ -117,6 +117,16 @@ 1 1 + + Строка + + Rows + 2 + 2 + -1 + -1 + + Шапка @@ -137,16 +147,6 @@ -1 - - Строка - - Rows - 2 - 2 - -1 - -1 - - 10 diff --git a/tests/skills/cases/mxl-validate/snapshots/valid-complex/Template.xml b/tests/skills/cases/mxl-validate/snapshots/valid-complex/Template.xml index 577c7213..4a993107 100644 --- a/tests/skills/cases/mxl-validate/snapshots/valid-complex/Template.xml +++ b/tests/skills/cases/mxl-validate/snapshots/valid-complex/Template.xml @@ -163,16 +163,6 @@ -1 - - ШапкаТаблицы - - Rows - 1 - 2 - -1 - -1 - - Строка @@ -183,6 +173,16 @@ -1 + + ШапкаТаблицы + + Rows + 1 + 2 + -1 + -1 + + Solid diff --git a/tests/skills/cases/mxl-validate/snapshots/valid-print-form/Template.xml b/tests/skills/cases/mxl-validate/snapshots/valid-print-form/Template.xml index e5cd5d93..97d074f3 100644 --- a/tests/skills/cases/mxl-validate/snapshots/valid-print-form/Template.xml +++ b/tests/skills/cases/mxl-validate/snapshots/valid-print-form/Template.xml @@ -138,16 +138,6 @@ -1 - - Строка - - Rows - 1 - 1 - -1 - -1 - - Итого @@ -158,6 +148,16 @@ -1 + + Строка + + Rows + 1 + 1 + -1 + -1 + + Solid