feat(mxl-compile,mxl-decompile,mxl-validate): группы строк и колонок

Документные ключи rowGroups и columnGroups описывают сворачиваемые
группы: диапазон, имя, признак свёрнутости и расположение заголовка.

Форма плоская, как в формате: вложенность выражена вхождением одного
диапазона в другой. Дерево было бы читаемее, но платформа хранит плоско,
и для правки чужого макета дерево дороже — чтобы добавить группу внутрь
существующей, пришлось бы искать родителя. Частичных пересечений
платформа не порождает (корпус: 40 620 886 пар непересекающихся,
599 958 вложенных, ни одного частичного), поэтому компилятор их
отвергает.

Число уровней вложенности не задаётся: оно совпало с <vgLevels> у всех
1797 макетов корпуса. Порядок записи компилятор приводит к
платформенному — родитель раньше вложенных, по возрастанию начала
(совпало у 1798 макетов из 1798).

Имя группы вывести нельзя, хотя 143 455 имён корпуса выглядят как
автоматические R<номер строки>: 16 666 из них протухли после вставки
строк, а 10 287 групп имени не имеют вовсе. Расположение заголовка взяло
ключ titleLocation — то же свойство и то же имя, что в DSL форм.

mxl-validate: проверка 14 — перевёрнутый диапазон, частичное пересечение,
начало группы за пределами документа.

Стенд Группировки собирается байт в байт обоими портами и добавлен
в платформенные фикстуры.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-15 15:50:18 +03:00
co-authored by Claude Opus 5
parent 4005f36cce
commit 1e0b82d419
19 changed files with 1173 additions and 13 deletions
+4 -1
View File
@@ -62,6 +62,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -J
{ col, span, rowspan, style, param, detail, text, template, valueType, controlType, value, note } { col, span, rowspan, style, param, detail, text, template, valueType, controlType, value, note }
]}]}], ]}]}],
namedAreas: [{ name, rows, cols }], namedAreas: [{ name, rows, cols }],
rowGroups: [{ rows, name, collapsed, titleLocation }],
columnGroups: [{ cols, name, collapsed, titleLocation }],
columnSets: { name: { columns, columnWidths, columnStyles } } columnSets: { name: { columns, columnWidths, columnStyles } }
} }
``` ```
@@ -80,8 +82,9 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -J
- `col` — 1-based позиция колонки - `col` — 1-based позиция колонки
- `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки) - `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки)
- Содержимое ячейки задаётся одним из ключей: `param` — параметр заполнения, `text` — статический текст, `template` — текст со вставками `[Параметр]` - Содержимое ячейки задаётся одним из ключей: `param` — параметр заполнения, `text` — статический текст, `template` — текст со вставками `[Параметр]`
- `rowGroups`/`columnGroups` — группы строк и колонок (сворачиваются кнопкой на полях): плоский список диапазонов, вложенность выражена вхождением одного в другой
- `note` — всплывающая подсказка у ячейки: строка, объект языков или `{ text, style, autoSize, box }` - `note` — всплывающая подсказка у ячейки: строка, объект языков или `{ text, style, autoSize, box }`
- `valueType` делает ячейку полем ввода (`"Number(15,3,nonneg)"`, `"String(10)"`, `"CatalogRef.Валюты"`, составной через ` + `); текста в такой ячейке быть не может. `value` — значение в поле, `controlType: "checkbox"` — флажок вместо поля ввода - `valueType` делает ячейку полем ввода (`"Number(15,3,nonneg)"`, `"String(10)"`, `"CatalogRef.Валюты"`, составной через ` + `); текста в такой ячейке быть не может, значение задаётся ключом `value`
Двухуровневая шапка массивами: Двухуровневая шапка массивами:
```json ```json
@@ -87,6 +87,8 @@
| `areas` | да | — | Массив областей — диапазонов подряд идущих строк (порядок = порядок в документе); имя необязательно | | `areas` | да | — | Массив областей — диапазонов подряд идущих строк (порядок = порядок в документе); имя необязательно |
| `namedAreas` | нет | — | Именованные области, заданные координатами (см. ниже) | | `namedAreas` | нет | — | Именованные области, заданные координатами (см. ниже) |
| `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `styles.md`) | | `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `styles.md`) |
| `rowGroups` | нет | — | Группы строк (см. ниже) |
| `columnGroups` | нет | — | Группы колонок (см. ниже) |
## Области (`areas[]`) ## Области (`areas[]`)
@@ -289,11 +291,37 @@
| `text` | — | Текст подсказки: строка или объект «язык → текст» | | `text` | — | Текст подсказки: строка или объект «язык → текст» |
| `style` | стиль подсказки | Имя стиля из `styles`; без него — оформление, которое даёт Конфигуратор | | `style` | стиль подсказки | Имя стиля из `styles`; без него — оформление, которое даёт Конфигуратор |
| `autoSize` | `true` | Подгонять ли размер окошка под текст | | `autoSize` | `true` | Подгонять ли размер окошка под текст |
| `box` | канонический | Смещения окошка: `top`, `left` — положение, `bottom`, `right` — размер | | `box` | | Смещения окошка: `top`, `left` — положение, `bottom`, `right` — размер. Без ключа окошко встаёт стандартно |
Координаты ячейки в примечании не задаются — платформа привязывает конец окошка к самой ячейке, Координаты ячейки в примечании не задаются. `autoSize` и `box` независимы: при автоподгоне
и компилятор проставляет это сам. `autoSize` и `box` независимы: при автоподгоне размера размера положение окошка всё равно хранится.
положение окошка всё равно хранится.
## Группы строк и колонок
Диапазон строк или колонок, который сворачивается кнопкой на полях. Задаются плоским списком;
вложенность выражена вхождением одного диапазона в другой:
```json
"rowGroups": [
{ "rows": "2-4" },
{ "rows": 3 },
{ "rows": 5, "name": "Итоговая", "collapsed": true, "titleLocation": "begin" }
],
"columnGroups": [
{ "cols": "2-3", "name": { "ru": "Показатели", "en": "Values" } },
{ "cols": 3 }
]
```
| Поле | Обяз. | По умолч. | Описание |
|------|:-----:|-----------|----------|
| `rows` / `cols` | да | — | Диапазон 1-based: число или `"N-M"` |
| `name` | нет | — | Имя группы: строка или объект «язык → текст» |
| `collapsed` | нет | `false` | Свёрнута ли группа при открытии |
| `titleLocation` | нет | `auto` | Расположение заголовка: `begin`, `end`, `auto` |
Диапазоны либо вложены, либо не пересекаются: частичное пересечение — ошибка. Порядок в списке
не важен, число уровней вложенности считается само.
## `rowStyle` — оформление строки ## `rowStyle` — оформление строки
@@ -311,7 +339,6 @@ XML не возвращается.
- объединения, не привязанные к ячейке (по всей высоте или ширине документа); - объединения, не привязанные к ячейке (по всей высоте или ширине документа);
- рисунки и картинки, в том числе штрихкоды; - рисунки и картинки, в том числе штрихкоды;
- группировки строк и колонок;
- колонтитулы, параметры печати, область печати. - колонтитулы, параметры печати, область печати.
Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому
@@ -1,4 +1,4 @@
# mxl-compile v1.43 — Compile 1C spreadsheet from JSON # mxl-compile v1.44 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -2037,11 +2037,124 @@ if ($def.namedAreas) {
} }
} }
# 7e-bis. Группировки строк и колонок. Платформа хранит их плоским списком диапазонов;
# вложенность выражена вхождением одного диапазона в другой, а частичных пересечений не
# бывает вовсе (корпус: 40 620 886 пар непересекающихся, 599 958 вложенных, 0 частичных).
function Get-Groups {
param([string]$key, [string]$axis)
$out = @()
$idx = 0
foreach ($g in $def.$key) {
$idx++
$where = "$key[$idx]"
$spec = "$($g.$axis)".Trim()
if (-not $spec) {
[Console]::Error.WriteLine("${key}: '$axis' is required: $where")
exit 1
}
if ($spec -match '^(\d+)\s*-\s*(\d+)$') {
$frm = [int]$Matches[1]; $to = [int]$Matches[2]
} elseif ($spec -match '^\d+$') {
$frm = [int]$spec; $to = $frm
} else {
[Console]::Error.WriteLine("${key}: invalid '$axis' value `"$spec`": $where")
exit 1
}
if ($frm -lt 1) { $frm = 1 }
if ($to -lt $frm) {
[Console]::Error.WriteLine("${key}: '$axis' range is reversed `"$spec`": $where")
exit 1
}
$header = ''
if ($null -ne $g.titleLocation -and "$($g.titleLocation)" -ne '') {
switch ("$($g.titleLocation)".Trim().ToLowerInvariant()) {
'auto' { $header = '' }
'begin' { $header = 'Begin' }
'end' { $header = 'End' }
default {
[Console]::Error.WriteLine("${key}: unknown 'titleLocation' value `"$($g.titleLocation)`" ($where). Allowed: begin, end, auto")
exit 1
}
}
}
$out += @{
B = $frm - 1; E = $to - 1
Name = $g.name
Collapsed = ($g.collapsed -eq $true -or "$($g.collapsed)" -eq 'true')
Header = $header
}
}
for ($i = 0; $i -lt $out.Count; $i++) {
for ($j = $i + 1; $j -lt $out.Count; $j++) {
$a = $out[$i]; $b = $out[$j]
if ($a.E -lt $b.B -or $b.E -lt $a.B) { continue }
if (($a.B -le $b.B -and $b.E -le $a.E) -or ($b.B -le $a.B -and $a.E -le $b.E)) { continue }
[Console]::Error.WriteLine("${key}: ranges must nest or be disjoint, got a partial overlap ($($a.B + 1)-$($a.E + 1) and $($b.B + 1)-$($b.E + 1))")
exit 1
}
}
# Порядок платформы — родитель раньше детей, по возрастанию начала.
return @($out | Sort-Object @{ Expression = { $_.B } }, @{ Expression = { -$_.E } })
}
function Get-GroupLevels {
# Глубина вложенности. На корпусе она совпала с <vgLevels> у всех 1797 макетов.
param($groups)
$depth = 0
for ($i = 0; $i -lt $groups.Count; $i++) {
$g = $groups[$i]; $d = 1
for ($j = 0; $j -lt $groups.Count; $j++) {
if ($j -eq $i) { continue }
$o = $groups[$j]
if ($o.B -le $g.B -and $g.E -le $o.E -and -not ($o.B -eq $g.B -and $o.E -eq $g.E)) { $d++ }
}
if ($d -gt $depth) { $depth = $d }
}
return $depth
}
function Emit-Groups {
param([string]$tag, $groups)
foreach ($g in $groups) {
X "`t<$tag>"
X "`t`t<b>$($g.B)</b>"
if ($g.E -ne $g.B) { X "`t`t<e>$($g.E)</e>" }
if ($null -ne $g.Name) {
$pairs = @()
if ($g.Name -is [System.Collections.IDictionary]) {
foreach ($k in $g.Name.Keys) { $pairs += @{ Lang = "$k"; Text = "$($g.Name[$k])" } }
} elseif ($g.Name -is [System.Management.Automation.PSCustomObject]) {
foreach ($pr in $g.Name.PSObject.Properties) { $pairs += @{ Lang = $pr.Name; Text = "$($pr.Value)" } }
} else {
foreach ($l in $textLanguages) { $pairs += @{ Lang = $l; Text = "$($g.Name)" } }
}
X "`t`t<t>"
foreach ($pr in $pairs) {
X "`t`t`t<v8:item>"
X "`t`t`t`t<v8:lang>$($pr.Lang)</v8:lang>"
X "`t`t`t`t<v8:content>$(Esc-XmlText $pr.Text)</v8:content>"
X "`t`t`t</v8:item>"
}
X "`t`t</t>"
}
# Развёрнутая группировка тега не пишет: на корпусе <o> только со значением false.
if ($g.Collapsed) { X "`t`t<o>false</o>" }
if ($g.Header) { X "`t`t<g>$($g.Header)</g>" }
X "`t</$tag>"
}
}
$rowGroups = Get-Groups 'rowGroups' 'rows'
$colGroups = Get-Groups 'columnGroups' 'cols'
# 7e. Scalar metadata # 7e. Scalar metadata
X "`t<templateMode>true</templateMode>" X "`t<templateMode>true</templateMode>"
X "`t<defaultFormatIndex>$defaultFormatIndex</defaultFormatIndex>" X "`t<defaultFormatIndex>$defaultFormatIndex</defaultFormatIndex>"
X "`t<height>$totalRowCount</height>" X "`t<height>$totalRowCount</height>"
if ($rowGroups.Count -gt 0) { X "`t<vgLevels>$(Get-GroupLevels $rowGroups)</vgLevels>" }
X "`t<vgRows>$totalRowCount</vgRows>" X "`t<vgRows>$totalRowCount</vgRows>"
Emit-Groups 'vg' $rowGroups
Emit-Groups 'hg' $colGroups
# 7f. Merges # 7f. Merges
foreach ($m in $merges) { foreach ($m in $merges) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# mxl-compile v1.43 — Compile 1C spreadsheet from JSON # mxl-compile v1.44 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import hashlib import hashlib
@@ -1953,11 +1953,115 @@ def main():
'EndCol': cols[1] - 1 if cols else -1, 'EndCol': cols[1] - 1 if cols else -1,
}) })
# 7e-bis. Группировки строк и колонок. Платформа хранит их плоским списком диапазонов;
# вложенность выражена вхождением одного диапазона в другой, а частичных пересечений не
# бывает вовсе (корпус: 40 620 886 пар непересекающихся, 599 958 вложенных, 0 частичных).
def parse_groups(key, axis):
out = []
for i, g in enumerate(defn.get(key) or [], start=1):
where = f'{key}[{i}]'
if not isinstance(g, dict):
print(f'{key}: element must be an object: {where}', file=sys.stderr)
sys.exit(1)
spec = g.get(axis)
if spec is None or str(spec).strip() == '':
print(f"{key}: '{axis}' is required: {where}", file=sys.stderr)
sys.exit(1)
spec = str(spec).strip()
m = re.match(r'^(\d+)\s*-\s*(\d+)$', spec)
if m:
frm, to = int(m.group(1)), int(m.group(2))
elif re.match(r'^\d+$', spec):
frm = to = int(spec)
else:
print(f"{key}: invalid '{axis}' value \"{spec}\": {where}", file=sys.stderr)
sys.exit(1)
if frm < 1:
frm = 1
if to < frm:
print(f"{key}: '{axis}' range is reversed \"{spec}\": {where}", file=sys.stderr)
sys.exit(1)
header = g.get('titleLocation')
header = str(header).strip().lower() if header is not None else ''
if header in ('', 'auto'):
header = ''
elif header == 'begin':
header = 'Begin'
elif header == 'end':
header = 'End'
else:
print(f"{key}: unknown 'titleLocation' value \"{g.get('titleLocation')}\""
f' ({where}). Allowed: begin, end, auto', file=sys.stderr)
sys.exit(1)
out.append({
'B': frm - 1, 'E': to - 1,
'Name': g.get('name'),
'Collapsed': g.get('collapsed') is True or str(g.get('collapsed')).lower() == 'true',
'Header': header,
})
for i in range(len(out)):
for j in range(i + 1, len(out)):
a, b = out[i], out[j]
if a['E'] < b['B'] or b['E'] < a['B']:
continue
if (a['B'] <= b['B'] and b['E'] <= a['E']) or (b['B'] <= a['B'] and a['E'] <= b['E']):
continue
print(f'{key}: ranges must nest or be disjoint, got a partial overlap'
f' ({a["B"] + 1}-{a["E"] + 1} and {b["B"] + 1}-{b["E"] + 1})', file=sys.stderr)
sys.exit(1)
# Порядок платформы — родитель раньше детей, по возрастанию начала.
out.sort(key=lambda x: (x['B'], -x['E']))
return out
row_groups = parse_groups('rowGroups', 'rows')
col_groups = parse_groups('columnGroups', 'cols')
def group_levels(groups):
"""Глубина вложенности. На корпусе она совпала с <vgLevels> у всех 1797 макетов."""
depth = 0
for i, g in enumerate(groups):
d = 1
for j, o in enumerate(groups):
if j != i and o['B'] <= g['B'] and g['E'] <= o['E'] and (o['B'], o['E']) != (g['B'], g['E']):
d += 1
depth = max(depth, d)
return depth
def emit_groups(lines, tag, groups):
for g in groups:
lines.append(f'\t<{tag}>')
lines.append(f'\t\t<b>{g["B"]}</b>')
if g['E'] != g['B']:
lines.append(f'\t\t<e>{g["E"]}</e>')
if g['Name'] is not None:
value = g['Name']
if isinstance(value, dict):
pairs = [(str(k), str(v)) for k, v in value.items()]
else:
pairs = [(lang, str(value)) for lang in text_languages]
lines.append('\t\t<t>')
for lang, content in pairs:
lines.append('\t\t\t<v8:item>')
lines.append(f'\t\t\t\t<v8:lang>{lang}</v8:lang>')
lines.append(f'\t\t\t\t<v8:content>{esc_xml_text(content)}</v8:content>')
lines.append('\t\t\t</v8:item>')
lines.append('\t\t</t>')
# Развёрнутая группировка тега не пишет: на корпусе <o> только со значением false.
if g['Collapsed']:
lines.append('\t\t<o>false</o>')
if g['Header']:
lines.append(f'\t\t<g>{g["Header"]}</g>')
lines.append(f'\t</{tag}>')
# 7e. Scalar metadata # 7e. Scalar metadata
lines.append(f'\t<templateMode>true</templateMode>') lines.append(f'\t<templateMode>true</templateMode>')
lines.append(f'\t<defaultFormatIndex>{default_format_index}</defaultFormatIndex>') lines.append(f'\t<defaultFormatIndex>{default_format_index}</defaultFormatIndex>')
lines.append(f'\t<height>{total_row_count}</height>') lines.append(f'\t<height>{total_row_count}</height>')
if row_groups:
lines.append(f'\t<vgLevels>{group_levels(row_groups)}</vgLevels>')
lines.append(f'\t<vgRows>{total_row_count}</vgRows>') lines.append(f'\t<vgRows>{total_row_count}</vgRows>')
emit_groups(lines, 'vg', row_groups)
emit_groups(lines, 'hg', col_groups)
# 7f. Merges # 7f. Merges
for m in merges: for m in merges:
@@ -1,4 +1,4 @@
# mxl-decompile v1.23 — Decompile 1C spreadsheet to JSON # mxl-decompile v1.24 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -265,6 +265,41 @@ function Get-Format {
return $rawFormats[$idx - 1] return $rawFormats[$idx - 1]
} }
# --- 4b. Группировки строк и колонок ---
# Платформа хранит их плоским списком диапазонов, родитель раньше детей. Число уровней
# (<vgLevels>) не читаем: оно выводится из вложенности — совпало на всех 1797 макетах.
function Read-Groups {
param([string]$tag, [string]$axis)
$out = @()
foreach ($g in $root.SelectNodes("d:$tag", $ns)) {
$bNode = $g.SelectSingleNode("d:b", $ns)
$b = if ($bNode) { [int]$bNode.InnerText } else { 0 }
$eNode = $g.SelectSingleNode("d:e", $ns)
$e = if ($eNode) { [int]$eNode.InnerText } else { $b }
$item = [ordered]@{}
$item[$axis] = if ($e -eq $b) { $b + 1 } else { "$($b + 1)-$($e + 1)" }
$tEl = $g.SelectSingleNode("d:t", $ns)
if ($tEl) {
$byLang = [ordered]@{}
foreach ($it in $tEl.SelectNodes("v8:item", $ns)) {
$l = $it.SelectSingleNode("v8:lang", $ns)
$cnt = $it.SelectSingleNode("v8:content", $ns)
$byLang[$(if ($l) { $l.InnerText } else { '' })] = $(if ($cnt) { $cnt.InnerText } else { '' })
}
if ($byLang.Count -gt 0) { $item["name"] = $byLang }
}
$oNode = $g.SelectSingleNode("d:o", $ns)
if ($oNode -and $oNode.InnerText.Trim() -ceq 'false') { $item["collapsed"] = $true }
$gNode = $g.SelectSingleNode("d:g", $ns)
if ($gNode -and $gNode.InnerText) { $item["titleLocation"] = $gNode.InnerText.Trim().ToLowerInvariant() }
$out += $item
}
return @($out)
}
$rowGroups = Read-Groups 'vg' 'rows'
$colGroups = Read-Groups 'hg' 'cols'
# --- 5. Extract columns and default width --- # --- 5. Extract columns and default width ---
# Колоночная раскладка («индивидуальная ширина колонок» для группы строк) — элемент <columns>. # Колоночная раскладка («индивидуальная ширина колонок» для группы строк) — элемент <columns>.
@@ -1435,6 +1470,13 @@ if ($extraSets.Count -gt 0) {
$result["columnSets"] = $setsOut $result["columnSets"] = $setsOut
} }
# Имя группировки — та же мультиязычная строка, что текст ячейки, и сворачивается так же.
foreach ($g in ($rowGroups + $colGroups)) {
if ($g.Contains("name")) { $g["name"] = Get-DslText $g["name"] }
}
if ($rowGroups.Count -gt 0) { $result["rowGroups"] = [array]$rowGroups }
if ($colGroups.Count -gt 0) { $result["columnGroups"] = [array]$colGroups }
$result["areas"] = [array]$dslAreas $result["areas"] = [array]$dslAreas
# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится # Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# mxl-decompile v1.23 — Decompile 1C spreadsheet to JSON # mxl-decompile v1.24 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -431,6 +431,36 @@ def main():
return None return None
return raw_formats[idx - 1] return raw_formats[idx - 1]
# --- 4b. Группировки строк и колонок ---
# Платформа хранит их плоским списком диапазонов, родитель раньше детей. Число уровней
# (<vgLevels>) не читаем: оно выводится из вложенности — совпало на всех 1797 макетах.
def read_groups(tag, axis):
out = []
for g in findall(root, "d:" + tag):
b = int_of(find(g, "d:b"))
e_node = find(g, "d:e")
e = int_of(e_node, b) if e_node is not None else b
item = OrderedDict()
item[axis] = (b + 1) if e == b else f'{b + 1}-{e + 1}'
t_el = find(g, "d:t")
if t_el is not None:
by_lang = OrderedDict()
for it in findall(t_el, "v8:item"):
lang = text_of(find(it, "v8:lang")) or ''
by_lang[lang] = text_of(find(it, "v8:content")) or ''
if by_lang:
item["name"] = by_lang
if (text_of(find(g, "d:o")) or 'true').strip() == 'false':
item["collapsed"] = True
place = text_of(find(g, "d:g"))
if place:
item["titleLocation"] = place.strip().lower()
out.append(item)
return out
row_groups = read_groups("vg", "rows")
col_groups = read_groups("hg", "cols")
# --- 5. Extract columns and default width --- # --- 5. Extract columns and default width ---
# Колоночная раскладка («индивидуальная ширина колонок» для группы строк) — элемент <columns>. # Колоночная раскладка («индивидуальная ширина колонок» для группы строк) — элемент <columns>.
@@ -1433,6 +1463,15 @@ def main():
sets_out[cs["Id"]] = entry sets_out[cs["Id"]] = entry
result["columnSets"] = sets_out result["columnSets"] = sets_out
# Имя группировки — та же мультиязычная строка, что текст ячейки, и сворачивается так же.
for _g in row_groups + col_groups:
if "name" in _g:
_g["name"] = get_dsl_text(_g["name"])
if row_groups:
result["rowGroups"] = row_groups
if col_groups:
result["columnGroups"] = col_groups
result["areas"] = dsl_areas result["areas"] = dsl_areas
# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится # Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится
@@ -1,4 +1,4 @@
# mxl-validate v1.5 — Validate 1C spreadsheet # mxl-validate v1.6 — Validate 1C spreadsheet
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Alias('Path')] [Alias('Path')]
@@ -507,6 +507,38 @@ if ($valueFormatIdx.Count -gt 0) {
Report-OK "Value cells: $($valueFormatIdx.Count) value formats" Report-OK "Value cells: $($valueFormatIdx.Count) value formats"
} }
# --- Check 14: группировки строк и колонок ---
# Диапазоны группировок либо вложены, либо не пересекаются: на корпусе 40 620 886 пар
# непересекающихся и 599 958 вложенных, частичных пересечений нет ни одного.
foreach ($tag in @('vg', 'hg')) {
$ranges = @()
foreach ($g in $root.SelectNodes("d:$tag", $nsMgr)) {
$bNode = $g.SelectSingleNode("d:b", $nsMgr)
if (-not $bNode) {
Report-Error "Group <$tag>: <b> is missing"
continue
}
$b = [int]$bNode.InnerText
$eNode = $g.SelectSingleNode("d:e", $nsMgr)
$e = if ($eNode) { [int]$eNode.InnerText } else { $b }
if ($e -lt $b) { Report-Error "Group <$tag> $b..${e}: range is reversed" }
if ($tag -ceq 'vg' -and $docHeight -gt 0 -and $b -ge $docHeight) {
Report-Warn "Group <vg> starts at row $b, beyond document height ($docHeight)"
}
$ranges += ,@($b, $e)
}
for ($i = 0; $i -lt $ranges.Count; $i++) {
for ($j = $i + 1; $j -lt $ranges.Count; $j++) {
$a = $ranges[$i]; $c = $ranges[$j]
if ($a[1] -lt $c[0] -or $c[1] -lt $a[0]) { continue }
if (($a[0] -le $c[0] -and $c[1] -le $a[1]) -or ($c[0] -le $a[0] -and $a[1] -le $c[1])) { continue }
Report-Error "Groups <$tag> $($a[0])..$($a[1]) and $($c[0])..$($c[1]) overlap partially"
}
}
if ($ranges.Count -gt 0) { Report-OK "Groups <$tag>: $($ranges.Count)" }
}
# --- Summary --- # --- Summary ---
$checks = $script:okCount + $errors + $warnings $checks = $script:okCount + $errors + $warnings
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# mxl-validate v1.5 — Validate 1C spreadsheet document Template.xml # mxl-validate v1.6 — Validate 1C spreadsheet document Template.xml
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges.""" """Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges."""
import sys, os, argparse import sys, os, argparse
@@ -494,6 +494,35 @@ def main():
if value_format_idx: if value_format_idx:
r.ok(f'Value cells: {len(value_format_idx)} value formats') r.ok(f'Value cells: {len(value_format_idx)} value formats')
# --- Check 14: группировки строк и колонок ---
# Диапазоны группировок либо вложены, либо не пересекаются: на корпусе 40 620 886 пар
# непересекающихся и 599 958 вложенных, частичных пересечений нет ни одного.
for tag in ('vg', 'hg'):
ranges = []
for g in root.findall(f'{{{NS_D}}}{tag}'):
b_node = g.find(f'{{{NS_D}}}b')
if b_node is None or not b_node.text:
r.error(f'Group <{tag}>: <b> is missing')
continue
b = int(b_node.text)
e_node = g.find(f'{{{NS_D}}}e')
e = int(e_node.text) if e_node is not None and e_node.text else b
if e < b:
r.error(f'Group <{tag}> {b}..{e}: range is reversed')
if tag == 'vg' and doc_height > 0 and b >= doc_height:
r.warn(f'Group <vg> starts at row {b}, beyond document height ({doc_height})')
ranges.append((b, e))
for i in range(len(ranges)):
for j in range(i + 1, len(ranges)):
a, c = ranges[i], ranges[j]
if a[1] < c[0] or c[1] < a[0]:
continue
if (a[0] <= c[0] and c[1] <= a[1]) or (c[0] <= a[0] and a[1] <= c[1]):
continue
r.error(f'Groups <{tag}> {a[0]}..{a[1]} and {c[0]}..{c[1]} overlap partially')
if ranges:
r.ok(f'Groups <{tag}>: {len(ranges)}')
# --- Finalize --- # --- Finalize ---
checks = r.ok_count + r.errors + r.warnings checks = r.ok_count + r.errors + r.warnings
if r.errors == 0 and r.warnings == 0 and not detailed: if r.errors == 0 and r.warnings == 0 and not detailed:
+32
View File
@@ -601,6 +601,38 @@ current-config не объявляет, поэтому вынести объяв
обратные, ячейки продолжают ссылаться на записи со старыми ширинами. Из итогового XML это обратные, ячейки продолжают ссылаться на записи со старыми ширинами. Из итогового XML это
значение не выводится — воспроизводить его не нужно. значение не выводится — воспроизводить его не нужно.
## Группировки строк и колонок
```xml
<vgLevels>2</vgLevels>
<vgRows>8</vgRows>
<vg>
<b>1</b> <!-- начало диапазона, 0-based -->
<e>2</e> <!-- конец; нет — группировка из одной строки -->
<t><v8:item><v8:lang>ru</v8:lang><v8:content>Итоговая</v8:content></v8:item></t>
<o>false</o> <!-- свёрнута; развёрнутая тег не пишет -->
<g>Begin</g> <!-- расположение заголовка: Begin | End; нет — Авто -->
</vg>
<hg></hg> <!-- то же для колонок -->
```
Строки группируются часто (1 797 макетов, 170 967 группировок), колонки — почти никогда
(1 макет, 1 группировка). Порядок документных тегов: `height``vgLevels``vgRows``vg`
`hg`; `vgLevels` пишется только при наличии группировок.
| Наблюдение | Данные корпуса |
|---|---|
| диапазоны либо вложены, либо не пересекаются | 40 620 886 пар непересекающихся, 599 958 вложенных, **0 частичных** |
| порядок записи — родитель раньше вложенных, по возрастанию начала | совпало у 1 798 макетов из 1 798 |
| `<vgLevels>` = глубина вложенности | совпало у 1 797 из 1 797 |
| `<o>` | только `false` (6 130) — развёрнутая группировка тег не пишет |
| `<g>` | в корпусе только `Begin` (6); значение `End` даёт Конфигуратор при «Расположение заголовка: Конец» |
Имя (`<t>`) — мультиязычная строка, и в 143 455 случаях это автоимя `R<номер строки + 1>`.
Вывести его нельзя: 16 666 имён «протухли» (`R91` у строки 103 — строки вставляли после
создания группировки), а 10 287 группировок имени не имеют вовсе. Языковые наборы —
`ru`+`en` (93 250) либо псевдоязык `#` (67 431), никогда вместе.
## Примечание к ячейке ## Примечание к ячейке
```xml ```xml
+31 -1
View File
@@ -87,6 +87,8 @@
| `areas` | да | — | Массив областей — диапазонов подряд идущих строк (порядок = порядок в документе); имя необязательно | | `areas` | да | — | Массив областей — диапазонов подряд идущих строк (порядок = порядок в документе); имя необязательно |
| `namedAreas` | нет | — | Именованные области, заданные координатами (см. ниже) | | `namedAreas` | нет | — | Именованные области, заданные координатами (см. ниже) |
| `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `mxl-dsl-styles.md`) | | `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `mxl-dsl-styles.md`) |
| `rowGroups` | нет | — | Группы строк (см. ниже) |
| `columnGroups` | нет | — | Группы колонок (см. ниже) |
## Области (`areas[]`) ## Области (`areas[]`)
@@ -300,6 +302,35 @@
и компилятор проставляет это сам. `autoSize` и `box` независимы: при автоподгоне размера и компилятор проставляет это сам. `autoSize` и `box` независимы: при автоподгоне размера
положение окошка всё равно хранится. положение окошка всё равно хранится.
## Группы строк и колонок
Диапазон строк или колонок, который сворачивается кнопкой на полях. Задаются плоским списком;
вложенность выражена вхождением одного диапазона в другой:
```json
"rowGroups": [
{ "rows": "2-4" },
{ "rows": 3 },
{ "rows": 5, "name": "Итоговая", "collapsed": true, "titleLocation": "begin" }
],
"columnGroups": [
{ "cols": "2-3", "name": { "ru": "Показатели", "en": "Values" } },
{ "cols": 3 }
]
```
| Поле | Обяз. | По умолч. | Описание |
|------|:-----:|-----------|----------|
| `rows` / `cols` | да | — | Диапазон 1-based: число или `"N-M"` |
| `name` | нет | — | Имя группы: строка или объект «язык → текст» |
| `collapsed` | нет | `false` | Свёрнута ли группа при открытии |
| `titleLocation` | нет | `auto` | Расположение заголовка: `begin`, `end`, `auto` |
Диапазоны либо вложены, либо не пересекаются — частичное пересечение платформа не хранит, и
компилятор отвергает его с ненулевым кодом выхода. Число уровней вложенности считается само.
Порядок в списке не важен: компилятор пишет группы так же, как платформа, — родитель
раньше вложенных, по возрастанию начала.
## `rowStyle` — оформление строки ## `rowStyle` — оформление строки
Стиль применяется ко ВСЕЙ ширине строки: позиции без явных ячеек получают тот же стиль. Так в табличных строках получаются сплошные рамки. Он же становится оформлением самой строки — именно так платформа хранит строку, оформленную целиком. Стиль применяется ко ВСЕЙ ширине строки: позиции без явных ячеек получают тот же стиль. Так в табличных строках получаются сплошные рамки. Он же становится оформлением самой строки — именно так платформа хранит строку, оформленную целиком.
@@ -316,7 +347,6 @@ XML не возвращается.
- объединения, не привязанные к ячейке (по всей высоте или ширине документа); - объединения, не привязанные к ячейке (по всей высоте или ширине документа);
- рисунки и картинки, в том числе штрихкоды; - рисунки и картинки, в том числе штрихкоды;
- группировки строк и колонок;
- колонтитулы, параметры печати, область печати. - колонтитулы, параметры печати, область печати.
Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому
@@ -0,0 +1,10 @@
{
"name": "Ошибка: группировки пересекаются частично",
"input": {
"columns": 2,
"areas": [{ "rows": [["a"], ["b"], ["c"], ["d"]] }],
"rowGroups": [{ "rows": "1-3" }, { "rows": "2-4" }]
},
"params": { "outputPath": "Template.xml" },
"expectError": "rowGroups: ranges must nest or be disjoint, got a partial overlap (1-3 and 2-4)"
}
@@ -0,0 +1,10 @@
{
"name": "Ошибка: неизвестное расположение заголовка группировки",
"input": {
"columns": 2,
"areas": [{ "rows": [["a"], ["b"]] }],
"rowGroups": [{ "rows": 2, "titleLocation": "middle" }]
},
"params": { "outputPath": "Template.xml" },
"expectError": "rowGroups: unknown 'titleLocation' value \"middle\" (rowGroups[1]). Allowed: begin, end, auto"
}
@@ -0,0 +1,236 @@
<?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>
<languageInfo>
<id>en</id>
<code>Английский</code>
<description>Английский</description>
</languageInfo>
</languageSettings>
<columns>
<size>3</size>
</columns>
<rowsItem>
<index>0</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>11</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>12</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>13</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>1</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>21</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>22</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>23</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>2</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>31</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>23</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>33</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>3</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>41</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>4</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>51</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>5</index>
<indexTo>7</indexTo>
<row>
<empty>true</empty>
</row>
</rowsItem>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>8</height>
<vgLevels>2</vgLevels>
<vgRows>8</vgRows>
<vg>
<b>1</b>
<e>2</e>
</vg>
<vg>
<b>2</b>
</vg>
<vg>
<b>3</b>
</vg>
<vg>
<b>4</b>
</vg>
<vg>
<b>5</b>
<t>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Произвеольное имя группы</v8:content>
</v8:item>
</t>
</vg>
<vg>
<b>6</b>
<t>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>ПИмяНачало</v8:content>
</v8:item>
</t>
<g>Begin</g>
</vg>
<vg>
<b>7</b>
<t>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>ПИмяКонец</v8:content>
</v8:item>
</t>
<g>End</g>
</vg>
<hg>
<b>1</b>
<e>2</e>
</hg>
<hg>
<b>2</b>
<t>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Горизонтальная</v8:content>
</v8:item>
</t>
</hg>
<format>
<width>72</width>
</format>
</document>
@@ -0,0 +1,15 @@
{
"name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: Группировки",
"setup": "fixture:platform-roundtrip",
"preRun": [
{
"script": "mxl-decompile/scripts/mxl-decompile",
"args": { "-TemplatePath": "Группировки.xml", "-OutputPath": "back.json" },
"cwd": "{workDir}"
}
],
"params": { "outputPath": "out.xml" },
"noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual",
"expect": { "filesEqual": { "actual": "out.xml", "expected": "Группировки.xml" } },
"inputFrom": "back.json"
}
@@ -0,0 +1,25 @@
{
"name": "Группировки строк и колонок",
"input": {
"columns": 3,
"areas": [
{ "rows": [
["11", "12", "13"],
["21", "22", "23"],
["31", "32", "33"],
["41"],
["51"]
]}
],
"rowGroups": [
{ "rows": "2-4" },
{ "rows": 3 },
{ "rows": 5, "name": "Итоговая", "collapsed": true, "titleLocation": "begin" }
],
"columnGroups": [
{ "cols": "2-3", "name": { "ru": "Показатели", "en": "Values" } },
{ "cols": 3, "titleLocation": "end" }
]
},
"params": { "outputPath": "Template.xml" }
}
@@ -0,0 +1,205 @@
<?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>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>11</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>12</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>13</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>1</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>21</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>22</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>23</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>2</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>31</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>32</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>33</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>3</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>41</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>4</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>51</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>5</height>
<vgLevels>2</vgLevels>
<vgRows>5</vgRows>
<vg>
<b>1</b>
<e>3</e>
</vg>
<vg>
<b>2</b>
</vg>
<vg>
<b>4</b>
<t>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Итоговая</v8:content>
</v8:item>
</t>
<o>false</o>
<g>Begin</g>
</vg>
<hg>
<b>1</b>
<e>2</e>
<t>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Показатели</v8:content>
</v8:item>
<v8:item>
<v8:lang>en</v8:lang>
<v8:content>Values</v8:content>
</v8:item>
</t>
</hg>
<hg>
<b>2</b>
<g>End</g>
</hg>
<format>
<width>10</width>
</format>
</document>
@@ -0,0 +1,28 @@
{
"name": "Roundtrip — группировки строк и колонок",
"preRun": [
{
"script": "mxl-compile/scripts/mxl-compile",
"input": {
"columns": 3,
"areas": [
{ "rows": [["11", "12", "13"], ["21", "22", "23"], ["31"], ["41"], ["51"]] }
],
"rowGroups": [
{ "rows": "2-4" },
{ "rows": 3 },
{ "rows": 5, "name": "Итоговая", "collapsed": true, "titleLocation": "begin" }
],
"columnGroups": [
{ "cols": "2-3", "name": { "#": "R2" } },
{ "cols": 3, "titleLocation": "end" }
]
},
"args": { "-JsonPath": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"args_extra": ["-OutputPath", "{workDir}/back.json"],
"expect": { "files": ["back.json"], "stdoutContains": "[OK] Decompiled:" }
}
@@ -0,0 +1,179 @@
<?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>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>11</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>12</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>13</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>1</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>21</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>22</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>23</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>2</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>31</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>3</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>41</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<rowsItem>
<index>4</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>51</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>5</height>
<vgLevels>2</vgLevels>
<vgRows>5</vgRows>
<vg>
<b>1</b>
<e>3</e>
</vg>
<vg>
<b>2</b>
</vg>
<vg>
<b>4</b>
<t>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Итоговая</v8:content>
</v8:item>
</t>
<o>false</o>
<g>Begin</g>
</vg>
<hg>
<b>1</b>
<e>2</e>
<t>
<v8:item>
<v8:lang>#</v8:lang>
<v8:content>R2</v8:content>
</v8:item>
</t>
</hg>
<hg>
<b>2</b>
<g>End</g>
</hg>
<format>
<width>10</width>
</format>
</document>
@@ -0,0 +1 @@
{ "columns": 3, "defaultWidth": 10, "fonts": {}, "styles": {}, "rowGroups": [{ "rows": "2-4" }, { "rows": 3 }, { "rows": 5, "name": "Итоговая", "collapsed": true, "titleLocation": "begin" }], "columnGroups": [{ "cols": "2-3", "name": { "#": "R2" } }, { "cols": 3, "titleLocation": "end" }], "areas": [{ "rows": [["11", "12", "13"], ["21", "22", "23"], ["31"], ["41"], ["51"]] }] }