feat(mxl-compile,mxl-decompile): безымянные блоки и области координатами

Инкремент 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) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-10 16:48:33 +03:00
co-authored by Claude Opus 5
parent f6a478c85c
commit 720ea05325
26 changed files with 977 additions and 188 deletions
+1
View File
@@ -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
@@ -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.<name>`)
@@ -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[]`)
| Поле | По умолч. | Описание |
@@ -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 "`t<templateMode>true</templateMode>"
X "`t<defaultFormatIndex>$defaultFormatIndex</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<namedItem xsi:type=`"NamedItemCells`">"
X "`t`t<name>$($ni.Name)</name>"
X "`t`t<area>"
X "`t`t`t<type>Rows</type>"
X "`t`t`t<type>$type</type>"
X "`t`t`t<beginRow>$($ni.BeginRow)</beginRow>"
X "`t`t`t<endRow>$($ni.EndRow)</endRow>"
X "`t`t`t<beginColumn>-1</beginColumn>"
X "`t`t`t<endColumn>-1</endColumn>"
X "`t`t`t<beginColumn>$($ni.BeginCol)</beginColumn>"
X "`t`t`t<endColumn>$($ni.EndCol)</endColumn>"
X "`t`t</area>"
X "`t</namedItem>"
}
+111 -11
View File
@@ -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'\t<templateMode>true</templateMode>')
lines.append(f'\t<defaultFormatIndex>{default_format_index}</defaultFormatIndex>')
@@ -1001,15 +1092,24 @@ def main():
lines.append('\t</merge>')
# 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<namedItem xsi:type="NamedItemCells">')
lines.append(f'\t\t<name>{ni["Name"]}</name>')
lines.append('\t\t<area>')
lines.append('\t\t\t<type>Rows</type>')
lines.append(f'\t\t\t<type>{area_type}</type>')
lines.append(f'\t\t\t<beginRow>{ni["BeginRow"]}</beginRow>')
lines.append(f'\t\t\t<endRow>{ni["EndRow"]}</endRow>')
lines.append('\t\t\t<beginColumn>-1</beginColumn>')
lines.append('\t\t\t<endColumn>-1</endColumn>')
lines.append(f'\t\t\t<beginColumn>{ni.get("BeginCol", -1)}</beginColumn>')
lines.append(f'\t\t\t<endColumn>{ni.get("EndCol", -1)}</endColumn>')
lines.append('\t\t</area>')
lines.append('\t</namedItem>')
@@ -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.
@@ -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)
+40 -2
View File
@@ -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.<name>`)
@@ -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[]`)
| Поле | По умолч. | Описание |
@@ -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] \"Разрывная\""
}
@@ -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] \"Пустая\""
}
@@ -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": ["<type>Rows</type>", "<type>Columns</type>", "<type>Rectangle</type>"]
}
}
}
@@ -76,16 +76,6 @@
<c>0</c>
<w>2</w>
</merge>
<namedItem xsi:type="NamedItemCells">
<name>Заголовок</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>2</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Данные</name>
<area>
@@ -96,6 +86,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Заголовок</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>2</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -102,16 +102,6 @@
<defaultFormatIndex>1</defaultFormatIndex>
<height>2</height>
<vgRows>2</vgRows>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>0</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
@@ -122,6 +112,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>0</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -136,16 +136,6 @@
<h>1</h>
<w>0</w>
</merge>
<namedItem xsi:type="NamedItemCells">
<name>Шапка</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
@@ -156,6 +146,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Шапка</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -156,11 +156,11 @@
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<name>Итого</name>
<area>
<type>Rows</type>
<beginRow>1</beginRow>
<endRow>1</endRow>
<beginRow>3</beginRow>
<endRow>3</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
@@ -176,11 +176,11 @@
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Итого</name>
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>3</beginRow>
<endRow>3</endRow>
<beginRow>1</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<languageSettings>
<currentLanguage>ru</currentLanguage>
<defaultLanguage>ru</defaultLanguage>
<languageInfo>
<id>ru</id>
<code>Русский</code>
<description>Русский</description>
</languageInfo>
</languageSettings>
<columns>
<size>4</size>
</columns>
<rowsItem>
<index>0</index>
<row>
<c>
<i>0</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>А</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<i>1</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Б</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<i>2</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>В</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<i>3</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Г</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>1</index>
<row>
<c>
<i>0</i>
<c>
<f>3</f>
<parameter>П1</parameter>
</c>
</c>
<c>
<i>1</i>
<c>
<f>3</f>
<parameter>П2</parameter>
</c>
</c>
<c>
<i>2</i>
<c>
<f>3</f>
<parameter>П3</parameter>
</c>
</c>
<c>
<i>3</i>
<c>
<f>3</f>
<parameter>П4</parameter>
</c>
</c>
</row>
</rowsItem>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>2</height>
<vgRows>2</vgRows>
<namedItem xsi:type="NamedItemCells">
<name>ПолосаКолонок</name>
<area>
<type>Columns</type>
<beginRow>-1</beginRow>
<endRow>-1</endRow>
<beginColumn>1</beginColumn>
<endColumn>3</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ПолосаСтрок</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Прямоугольник</name>
<area>
<type>Rectangle</type>
<beginRow>1</beginRow>
<endRow>1</endRow>
<beginColumn>1</beginColumn>
<endColumn>2</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Ячейка</name>
<area>
<type>Rectangle</type>
<beginRow>0</beginRow>
<endRow>0</endRow>
<beginColumn>0</beginColumn>
<endColumn>0</endColumn>
</area>
</namedItem>
<font faceName="Arial" height="10" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
<format>
<width>10</width>
</format>
<format>
<font>0</font>
<fillType>Text</fillType>
</format>
<format>
<font>0</font>
<fillType>Parameter</fillType>
</format>
</document>
@@ -151,16 +151,6 @@
<defaultFormatIndex>1</defaultFormatIndex>
<height>2</height>
<vgRows>2</vgRows>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>0</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
@@ -171,6 +161,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>0</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -110,16 +110,6 @@
<c>1</c>
<w>1</w>
</merge>
<namedItem xsi:type="NamedItemCells">
<name>Шапка</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Тело</name>
<area>
@@ -130,6 +120,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Шапка</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -501,26 +501,6 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>4</beginRow>
<endRow>4</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>5</beginRow>
<endRow>5</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Итого</name>
<area>
@@ -541,6 +521,26 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>5</beginRow>
<endRow>5</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>4</beginRow>
<endRow>4</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -234,6 +234,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Итого</name>
<area>
<type>Rows</type>
<beginRow>4</beginRow>
<endRow>4</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Подпись</name>
<area>
@@ -245,11 +255,11 @@
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<name>Примечание</name>
<area>
<type>Rows</type>
<beginRow>2</beginRow>
<endRow>2</endRow>
<beginRow>5</beginRow>
<endRow>5</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
@@ -265,21 +275,11 @@
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Итого</name>
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>4</beginRow>
<endRow>4</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Примечание</name>
<area>
<type>Rows</type>
<beginRow>5</beginRow>
<endRow>5</endRow>
<beginRow>2</beginRow>
<endRow>2</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<languageSettings>
<currentLanguage>ru</currentLanguage>
<defaultLanguage>ru</defaultLanguage>
<languageInfo>
<id>ru</id>
<code>Русский</code>
<description>Русский</description>
</languageInfo>
</languageSettings>
<columns>
<size>3</size>
</columns>
<rowsItem>
<index>0</index>
<row>
<c>
<i>0</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>А</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<i>1</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Б</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<i>2</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>В</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>1</index>
<row>
<c>
<i>0</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>строки вне областей</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>2</index>
<row>
<c>
<i>0</i>
<c>
<f>3</f>
<parameter>П1</parameter>
</c>
</c>
<c>
<i>1</i>
<c>
<f>3</f>
<parameter>П2</parameter>
</c>
</c>
<c>
<i>2</i>
<c>
<f>3</f>
<parameter>П3</parameter>
</c>
</c>
</row>
</rowsItem>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>3</height>
<vgRows>3</vgRows>
<merge>
<r>1</r>
<c>0</c>
<w>2</w>
</merge>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>2</beginRow>
<endRow>2</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Шапка</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>0</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<font faceName="Arial" height="10" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
<format>
<width>10</width>
</format>
<format>
<font>0</font>
<fillType>Text</fillType>
</format>
<format>
<font>0</font>
<fillType>Parameter</fillType>
</format>
</document>
@@ -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": ["<name>Шапка</name>", "<name>Строка</name>", "строки вне областей"]
}
}
}
@@ -101,16 +101,6 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>1</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Итого</name>
<area>
@@ -121,6 +111,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>1</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -143,6 +143,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Итого</name>
<area>
<type>Rows</type>
<beginRow>3</beginRow>
<endRow>3</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Поставщик</name>
<area>
@@ -163,16 +173,6 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Итого</name>
<area>
<type>Rows</type>
<beginRow>3</beginRow>
<endRow>3</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -117,6 +117,16 @@
<c>1</c>
<w>1</w>
</merge>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>2</beginRow>
<endRow>2</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Шапка</name>
<area>
@@ -137,16 +147,6 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>2</beginRow>
<endRow>2</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<font faceName="Arial" height="10" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
<format>
<width>10</width>
@@ -163,16 +163,6 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>1</beginRow>
<endRow>2</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
@@ -183,6 +173,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>ШапкаТаблицы</name>
<area>
<type>Rows</type>
<beginRow>1</beginRow>
<endRow>2</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>
@@ -138,16 +138,6 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>1</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Итого</name>
<area>
@@ -158,6 +148,16 @@
<endColumn>-1</endColumn>
</area>
</namedItem>
<namedItem xsi:type="NamedItemCells">
<name>Строка</name>
<area>
<type>Rows</type>
<beginRow>1</beginRow>
<endRow>1</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<line width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</line>